Files
All-MQL5-code/Include/Math/Alglib/dataanalysis.mqh
T
2018-03-09 16:43:19 +01:00

15238 lines
559 KiB
Plaintext

//+------------------------------------------------------------------+
//| dataanalysis.mqh |
//| Copyright 2003-2012 Sergey Bochkanov (ALGLIB project) |
//| Copyright 2012-2017, MetaQuotes Software Corp. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
//| Implementation of ALGLIB library in MetaQuotes Language 5 |
//| |
//| The features of the library include: |
//| - Linear algebra (direct algorithms, EVD, SVD) |
//| - Solving systems of linear and non-linear equations |
//| - Interpolation |
//| - Optimization |
//| - FFT (Fast Fourier Transform) |
//| - Numerical integration |
//| - Linear and nonlinear least-squares fitting |
//| - Ordinary differential equations |
//| - Computation of special functions |
//| - Descriptive statistics and hypothesis testing |
//| - Data analysis - classification, regression |
//| - Implementing linear algebra algorithms, interpolation, etc. |
//| in high-precision arithmetic (using MPFR) |
//| |
//| This file is free software; you can redistribute it and/or |
//| modify it under the terms of the GNU General Public License as |
//| published by the Free Software Foundation (www.fsf.org); either |
//| version 2 of the License, or (at your option) any later version. |
//| |
//| This program is distributed in the hope that it will be useful, |
//| but WITHOUT ANY WARRANTY; without even the implied warranty of |
//| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
//| GNU General Public License for more details. |
//+------------------------------------------------------------------+
#include "ap.mqh"
#include "optimization.mqh"
#include "statistics.mqh"
#include "solvers.mqh"
//+------------------------------------------------------------------+
//| Auxiliary class for CBdSS |
//+------------------------------------------------------------------+
class CCVReport
{
public:
double m_relclserror;
double m_avgce;
double m_rmserror;
double m_avgerror;
double m_avgrelerror;
CCVReport(void);
~CCVReport(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CCVReport::CCVReport(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CCVReport::~CCVReport(void)
{
}
//+------------------------------------------------------------------+
//| Data analysis |
//+------------------------------------------------------------------+
class CBdSS
{
private:
//--- private methods
static double XLnY(const double x,const double y);
static double GetCV(int &cnt[],const int nc);
static void TieAddC(int &c[],int &ties[],const int ntie,const int nc,int &cnt[]);
static void TieSubC(int &c[],int &ties[],const int ntie,const int nc,int &cnt[]);
public:
//--- constructor, destructor
CBdSS(void);
~CBdSS(void);
//--- public methods
static void DSErrAllocate(const int nclasses,double &buf[]);
static void DSErrAccumulate(double &buf[],double &y[],double &desiredy[]);
static void DSErrFinish(double &buf[]);
static void DSNormalize(CMatrixDouble &xy,const int npoints,const int nvars,int &info,double &means[],double &sigmas[]);
static void DSNormalizeC(CMatrixDouble &xy,const int npoints,const int nvars,int &info,double &means[],double &sigmas[]);
static double DSGetMeanMindIstance(CMatrixDouble &xy,const int npoints,const int nvars);
static void DSTie(double &a[],const int n,int &ties[],int &tiecount,int &p1[],int &p2[]);
static void DSTieFastI(double &a[],int &b[],const int n,int &ties[],int &tiecount,double &bufr[],int &bufi[]);
static void DSOptimalSplit2(double &ca[],int &cc[],const int n,int &info,double &threshold,double &pal,double &pbl,double &par,double &pbr,double &cve);
static void DSOptimalSplit2Fast(double &a[],int &c[],int &tiesbuf[],int &cntbuf[],double &bufr[],int &bufi[],const int n,const int nc,double alpha,int &info,double &threshold,double &rms,double &cvrms);
static void DSSplitK(double &ca[],int &cc[],const int n,const int nc,int kmax,int &info,double &thresholds[],int &ni,double &cve);
static void DSOptimalSplitK(double &ca[],int &cc[],const int n,const int nc,int kmax,int &info,double &thresholds[],int &ni,double &cve);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CBdSS::CBdSS(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CBdSS::~CBdSS(void)
{
}
//+------------------------------------------------------------------+
//| This set of routines (DSErrAllocate, DSErrAccumulate, |
//| DSErrFinish) calculates different error functions (classification|
//| error, cross-entropy, rms, avg, avg.rel errors). |
//| 1. DSErrAllocate prepares buffer. |
//| 2. DSErrAccumulate accumulates individual errors: |
//| * Y contains predicted output (posterior probabilities for |
//| classification) |
//| * DesiredY contains desired output (class number for |
//| classification) |
//| 3. DSErrFinish outputs results: |
//| * Buf[0] contains relative classification error (zero for |
//| regression tasks) |
//| * Buf[1] contains avg. cross-entropy (zero for regression |
//| tasks) |
//| * Buf[2] contains rms error (regression, classification) |
//| * Buf[3] contains average error (regression, classification) |
//| * Buf[4] contains average relative error (regression, |
//| classification) |
//| NOTES(1): |
//| "NClasses>0" means that we have classification task. |
//| "NClasses<0" means regression task with -NClasses real |
//| outputs. |
//| NOTES(2): |
//| rms. avg, avg.rel errors for classification tasks are |
//| interpreted as errors in posterior probabilities with |
//| respect to probabilities given by training/test set. |
//+------------------------------------------------------------------+
static void CBdSS::DSErrAllocate(const int nclasses,double &buf[])
{
//--- allocation
ArrayResizeAL(buf,8);
//--- initialization
buf[0]=0;
buf[1]=0;
buf[2]=0;
buf[3]=0;
buf[4]=0;
buf[5]=nclasses;
buf[6]=0;
buf[7]=0;
}
//+------------------------------------------------------------------+
//| See DSErrAllocate for comments on this routine. |
//+------------------------------------------------------------------+
static void CBdSS::DSErrAccumulate(double &buf[],double &y[],double &desiredy[])
{
//--- create variables
int nclasses=0;
int nout=0;
int offs=0;
int mmax=0;
int rmax=0;
int j=0;
double v=0;
double ev=0;
//--- initialization
offs=5;
nclasses=(int)MathRound(buf[offs]);
//--- check
if(nclasses>0)
{
//--- Classification
rmax=(int)MathRound(desiredy[0]);
mmax=0;
//--- initialization
for(j=1;j<=nclasses-1;j++)
{
//--- check
if(y[j]>y[mmax])
mmax=j;
}
//--- check
if(mmax!=rmax)
buf[0]=buf[0]+1;
//--- check
if(y[rmax]>0.0)
buf[1]=buf[1]-MathLog(y[rmax]);
else
buf[1]=buf[1]+MathLog(CMath::m_maxrealnumber);
//--- calculation
for(j=0;j<=nclasses-1;j++)
{
v=y[j];
//--- check
if(j==rmax)
ev=1;
else
ev=0;
//--- change values
buf[2]=buf[2]+CMath::Sqr(v-ev);
buf[3]=buf[3]+MathAbs(v-ev);
//--- check
if(ev!=0.0)
{
buf[4]=buf[4]+MathAbs((v-ev)/ev);
buf[offs+2]=buf[offs+2]+1;
}
}
//--- change value
buf[offs+1]=buf[offs+1]+1;
}
else
{
//--- Regression
nout=-nclasses;
rmax=0;
//--- initialization
for(j=1;j<=nout-1;j++)
{
//--- check
if(desiredy[j]>desiredy[rmax])
rmax=j;
}
//--- initialization
mmax=0;
for(j=1;j<=nout-1;j++)
{
//--- check
if(y[j]>y[mmax])
mmax=j;
}
//--- check
if(mmax!=rmax)
buf[0]=buf[0]+1;
//--- calculation
for(j=0;j<=nout-1;j++)
{
//--- change values
v=y[j];
ev=desiredy[j];
buf[2]=buf[2]+CMath::Sqr(v-ev);
buf[3]=buf[3]+MathAbs(v-ev);
//--- check
if(ev!=0.0)
{
buf[4]=buf[4]+MathAbs((v-ev)/ev);
buf[offs+2]=buf[offs+2]+1;
}
}
//--- change value
buf[offs+1]=buf[offs+1]+1;
}
}
//+------------------------------------------------------------------+
//| See DSErrAllocate for comments on this routine. |
//+------------------------------------------------------------------+
static void CBdSS::DSErrFinish(double &buf[])
{
//--- create variables
int nout=0;
int offs=0;
//--- initialization
offs=5;
nout=(int)(MathAbs((int)MathRound(buf[offs])));
//--- check
if(buf[offs+1]!=0.0)
{
//--- change values
buf[0]=buf[0]/buf[offs+1];
buf[1]=buf[1]/buf[offs+1];
buf[2]=MathSqrt(buf[2]/(nout*buf[offs+1]));
buf[3]=buf[3]/(nout*buf[offs+1]);
}
//--- check
if(buf[offs+2]!=0.0)
buf[4]=buf[4]/buf[offs+2];
}
//+------------------------------------------------------------------+
//| Normalize |
//+------------------------------------------------------------------+
static void CBdSS::DSNormalize(CMatrixDouble &xy,const int npoints,
const int nvars,int &info,double &means[],
double &sigmas[])
{
//--- create variables
int i=0;
int j=0;
double mean=0;
double variance=0;
double skewness=0;
double kurtosis=0;
int i_=0;
//--- create array
double tmp[];
//--- initialization
info=0;
//--- Test parameters
if(npoints<=0 || nvars<1)
{
info=-1;
return;
}
//--- change value
info=1;
//--- Standartization
ArrayResizeAL(means,nvars);
ArrayResizeAL(sigmas,nvars);
ArrayResizeAL(tmp,npoints);
//--- calculation
for(j=0;j<=nvars-1;j++)
{
//--- copy
for(i_=0;i_<=npoints-1;i_++)
tmp[i_]=xy[i_][j];
//--- function call
CBaseStat::SampleMoments(tmp,npoints,mean,variance,skewness,kurtosis);
//--- change values
means[j]=mean;
sigmas[j]=MathSqrt(variance);
//--- check
if(sigmas[j]==0.0)
sigmas[j]=1;
//--- change values
for(i=0;i<=npoints-1;i++)
xy[i].Set(j,(xy[i][j]-means[j])/sigmas[j]);
}
}
//+------------------------------------------------------------------+
//| Normalize |
//+------------------------------------------------------------------+
static void CBdSS::DSNormalizeC(CMatrixDouble &xy,const int npoints,
const int nvars,int &info,double &means[],
double &sigmas[])
{
//--- create variables
int j=0;
double mean=0;
double variance=0;
double skewness=0;
double kurtosis=0;
int i_=0;
//--- create array
double tmp[];
//--- initialization
info=0;
//--- Test parameters
if(npoints<=0 || nvars<1)
{
info=-1;
return;
}
//--- change value
info=1;
//--- Standartization
ArrayResizeAL(means,nvars);
ArrayResizeAL(sigmas,nvars);
ArrayResizeAL(tmp,npoints);
for(j=0;j<=nvars-1;j++)
{
//--- copy
for(i_=0;i_<=npoints-1;i_++)
tmp[i_]=xy[i_][j];
//--- function call
CBaseStat::SampleMoments(tmp,npoints,mean,variance,skewness,kurtosis);
//--- change values
means[j]=mean;
sigmas[j]=MathSqrt(variance);
//--- check
if(sigmas[j]==0.0)
sigmas[j]=1;
}
}
//+------------------------------------------------------------------+
//| Method |
//+------------------------------------------------------------------+
static double CBdSS::DSGetMeanMindIstance(CMatrixDouble &xy,const int npoints,
const int nvars)
{
//--- create variables
double result=0;
int i=0;
int j=0;
double v=0;
int i_=0;
//--- creating arrays
double tmp[];
double tmp2[];
//--- Test parameters
if(npoints<=0 || nvars<1)
return(0);
//--- Process
ArrayResizeAL(tmp,npoints);
for(i=0;i<=npoints-1;i++)
tmp[i]=CMath::m_maxrealnumber;
//--- allocation
ArrayResizeAL(tmp2,nvars);
for(i=0;i<=npoints-1;i++)
{
for(j=i+1;j<=npoints-1;j++)
{
//--- calculation
for(i_=0;i_<=nvars-1;i_++)
tmp2[i_]=xy[i][i_];
for(i_=0;i_<=nvars-1;i_++)
tmp2[i_]=tmp2[i_]-xy[j][i_];
v=0.0;
for(i_=0;i_<=nvars-1;i_++)
v+=tmp2[i_]*tmp2[i_];
//--- change values
v=MathSqrt(v);
tmp[i]=MathMin(tmp[i],v);
tmp[j]=MathMin(tmp[j],v);
}
}
//--- get result
result=0;
for(i=0;i<=npoints-1;i++)
result=result+tmp[i]/npoints;
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Method |
//+------------------------------------------------------------------+
static void CBdSS::DSTie(double &a[],const int n,int &ties[],int &tiecount,
int &p1[],int &p2[])
{
//--- create variables
int i=0;
int k=0;
//--- create array
int tmp[];
//--- initialization
tiecount=0;
//--- Special case
if(n<=0)
{
tiecount=0;
return;
}
//--- Sort A
CTSort::TagSort(a,n,p1,p2);
//--- Process ties
tiecount=1;
for(i=1;i<=n-1;i++)
{
//--- check
if(a[i]!=a[i-1])
tiecount=tiecount+1;
}
//--- allocation
ArrayResizeAL(ties,tiecount+1);
//--- change values
ties[0]=0;
k=1;
//--- calculation
for(i=1;i<=n-1;i++)
{
//--- check
if(a[i]!=a[i-1])
{
ties[k]=i;
k=k+1;
}
}
//--- change value
ties[tiecount]=n;
}
//+------------------------------------------------------------------+
//| Method |
//+------------------------------------------------------------------+
static void CBdSS::DSTieFastI(double &a[],int &b[],const int n,int &ties[],
int &tiecount,double &bufr[],int &bufi[])
{
//--- create variables
int i=0;
int k=0;
//--- create array
int tmp[];
//--- initialization
tiecount=0;
//--- Special case
if(n<=0)
{
tiecount=0;
return;
}
//--- Sort A
CTSort::TagSortFastI(a,b,bufr,bufi,n);
//--- Process ties
ties[0]=0;
k=1;
//--- calculation
for(i=1;i<=n-1;i++)
{
//--- check
if(a[i]!=a[i-1])
{
ties[k]=i;
k=k+1;
}
}
//--- change values
ties[k]=n;
tiecount=k;
}
//+------------------------------------------------------------------+
//| Optimal binary classification |
//| Algorithms finds optimal (=with minimal cross-entropy) binary |
//| partition. |
//| Internal subroutine. |
//| INPUT PARAMETERS: |
//| A - array[0..N-1], variable |
//| C - array[0..N-1], class numbers (0 or 1). |
//| N - array size |
//| OUTPUT PARAMETERS: |
//| Info - completetion code: |
//| * -3, all values of A[] are same (partition is |
//| impossible) |
//| * -2, one of C[] is incorrect (<0, >1) |
//| * -1, incorrect pararemets were passed (N<=0). |
//| * 1, OK |
//| Threshold- partiton boundary. Left part contains values |
//| which are strictly less than Threshold. Right |
//| part contains values which are greater than or |
//| equal to Threshold. |
//| PAL, PBL- probabilities P(0|v<Threshold) and |
//| P(1|v<Threshold) |
//| PAR, PBR- probabilities P(0|v>=Threshold) and |
//| P(1|v>=Threshold) |
//| CVE - cross-validation estimate of cross-entropy |
//+------------------------------------------------------------------+
static void CBdSS::DSOptimalSplit2(double &ca[],int &cc[],const int n,
int &info,double &threshold,double &pal,
double &pbl,double &par,double &pbr,
double &cve)
{
//--- create variables
int i=0;
int t=0;
double s=0;
int tiecount=0;
int k=0;
int koptimal=0;
double pak=0;
double pbk=0;
double cvoptimal=0;
double cv=0;
//--- creating arrays
int ties[];
int p1[];
int p2[];
double a[];
int c[];
//--- copy
ArrayCopy(a,ca);
ArrayCopy(c,cc);
//--- initialization
info=0;
threshold=0;
pal=0;
pbl=0;
par=0;
pbr=0;
cve=0;
//--- Test for errors in inputs
if(n<=0)
{
info=-1;
return;
}
for(i=0;i<=n-1;i++)
{
//--- check
if(c[i]!=0 && c[i]!=1)
{
info=-2;
return;
}
}
//--- change value
info=1;
//--- Tie
DSTie(a,n,ties,tiecount,p1,p2);
//--- swap
for(i=0;i<=n-1;i++)
{
//--- check
if(p2[i]!=i)
{
t=c[i];
c[i]=c[p2[i]];
c[p2[i]]=t;
}
}
//--- Special case: number of ties is 1.
//--- NOTE: we assume that P[i][j] equals to 0 or 1,
//--- intermediate values are not allowed.
if(tiecount==1)
{
info=-3;
return;
}
//--- General case,number of ties > 1
//--- NOTE: we assume that P[i][j] equals to 0 or 1,
//--- intermediate values are not allowed.
pal=0;
pbl=0;
par=0;
pbr=0;
for(i=0;i<=n-1;i++)
{
//--- check
if(c[i]==0)
par=par+1;
//--- check
if(c[i]==1)
pbr=pbr+1;
}
//--- change values
koptimal=-1;
cvoptimal=CMath::m_maxrealnumber;
for(k=0;k<=tiecount-2;k++)
{
//--- first,obtain information about K-th tie which is
//--- moved from R-part to L-part
pak=0;
pbk=0;
for(i=ties[k];i<=ties[k+1]-1;i++)
{
//--- check
if(c[i]==0)
pak=pak+1;
//--- check
if(c[i]==1)
pbk=pbk+1;
}
//--- Calculate cross-validation CE
cv=0;
cv=cv-XLnY(pal+pak,(pal+pak)/(pal+pak+pbl+pbk+1));
cv=cv-XLnY(pbl+pbk,(pbl+pbk)/(pal+pak+1+pbl+pbk));
cv=cv-XLnY(par-pak,(par-pak)/(par-pak+pbr-pbk+1));
cv=cv-XLnY(pbr-pbk,(pbr-pbk)/(par-pak+1+pbr-pbk));
//--- Compare with best
if(cv<cvoptimal)
{
cvoptimal=cv;
koptimal=k;
}
//--- update
pal=pal+pak;
pbl=pbl+pbk;
par=par-pak;
pbr=pbr-pbk;
}
//--- change values
cve=cvoptimal;
threshold=0.5*(a[ties[koptimal]]+a[ties[koptimal+1]]);
pal=0;
pbl=0;
par=0;
pbr=0;
for(i=0;i<=n-1;i++)
{
//--- check
if(a[i]<threshold)
{
//--- check
if(c[i]==0)
pal=pal+1;
else
pbl=pbl+1;
}
else
{
//--- check
if(c[i]==0)
par=par+1;
else
pbr=pbr+1;
}
}
//--- change values
s=pal+pbl;
pal=pal/s;
pbl=pbl/s;
s=par+pbr;
par=par/s;
pbr=pbr/s;
}
//+------------------------------------------------------------------+
//| Optimal partition, internal subroutine. Fast version. |
//| Accepts: |
//| A array[0..N-1] array of attributes array[0..N-1]|
//| C array[0..N-1] array of class labels |
//| TiesBuf array[0..N] temporaries (ties) |
//| CntBuf array[0..2*NC-1] temporaries (counts) |
//| Alpha centering factor (0<=alpha<=1, |
//| recommended value - 0.05) |
//| BufR array[0..N-1] temporaries |
//| BufI array[0..N-1] temporaries |
//| Output: |
//| Info error code (">0"=OK, "<0"=bad) |
//| RMS training set RMS error |
//| CVRMS leave-one-out RMS error |
//| Note: |
//| content of all arrays is changed by subroutine; |
//| it doesn't allocate temporaries. |
//+------------------------------------------------------------------+
static void CBdSS::DSOptimalSplit2Fast(double &a[],int &c[],int &tiesbuf[],
int &cntbuf[],double &bufr[],int &bufi[],
const int n,const int nc,double alpha,
int &info,double &threshold,
double &rms,double &cvrms)
{
//--- create variables
int i=0;
int k=0;
int cl=0;
int tiecount=0;
double cbest=0;
double cc=0;
int koptimal=0;
int sl=0;
int sr=0;
double v=0;
double w=0;
double x=0;
//--- initialization
info=0;
threshold=0;
rms=0;
cvrms=0;
//--- Test for errors in inputs
if(n<=0 || nc<2)
{
info=-1;
return;
}
for(i=0;i<=n-1;i++)
{
//--- check
if(c[i]<0 || c[i]>=nc)
{
info=-2;
return;
}
}
//--- change value
info=1;
//--- Tie
DSTieFastI(a,c,n,tiesbuf,tiecount,bufr,bufi);
//--- Special case: number of ties is 1.
if(tiecount==1)
{
info=-3;
return;
}
//--- General case,number of ties > 1
for(i=0;i<=2*nc-1;i++)
cntbuf[i]=0;
for(i=0;i<=n-1;i++)
cntbuf[nc+c[i]]=cntbuf[nc+c[i]]+1;
//--- change values
koptimal=-1;
threshold=a[n-1];
cbest=CMath::m_maxrealnumber;
sl=0;
sr=n;
//--- calculation
for(k=0;k<=tiecount-2;k++)
{
//--- first,move Kth tie from right to left
for(i=tiesbuf[k];i<=tiesbuf[k+1]-1;i++)
{
cl=c[i];
cntbuf[cl]=cntbuf[cl]+1;
cntbuf[nc+cl]=cntbuf[nc+cl]-1;
}
sl=sl+(tiesbuf[k+1]-tiesbuf[k]);
sr=sr-(tiesbuf[k+1]-tiesbuf[k]);
//--- Calculate RMS error
v=0;
for(i=0;i<=nc-1;i++)
{
w=cntbuf[i];
v=v+w*CMath::Sqr(w/sl-1);
v=v+(sl-w)*CMath::Sqr(w/sl);
w=cntbuf[nc+i];
v=v+w*CMath::Sqr(w/sr-1);
v=v+(sr-w)*CMath::Sqr(w/sr);
}
//--- change value
v=MathSqrt(v/(nc*n));
//--- Compare with best
x=(double)(2*sl)/(double)(sl+sr)-1;
cc=v*(1-alpha+alpha*CMath::Sqr(x));
//--- check
if(cc<cbest)
{
//--- store split
rms=v;
koptimal=k;
cbest=cc;
//--- calculate CVRMS error
cvrms=0;
for(i=0;i<=nc-1;i++)
{
//--- check
if(sl>1)
{
w=cntbuf[i];
cvrms=cvrms+w*CMath::Sqr((w-1)/(sl-1)-1);
cvrms=cvrms+(sl-w)*CMath::Sqr(w/(sl-1));
}
else
{
w=cntbuf[i];
cvrms=cvrms+w*CMath::Sqr(1.0/(double)nc-1);
cvrms=cvrms+(sl-w)*CMath::Sqr(1.0/(double)nc);
}
//--- check
if(sr>1)
{
w=cntbuf[nc+i];
cvrms=cvrms+w*CMath::Sqr((w-1)/(sr-1)-1);
cvrms=cvrms+(sr-w)*CMath::Sqr(w/(sr-1));
}
else
{
w=cntbuf[nc+i];
cvrms=cvrms+w*CMath::Sqr(1.0/(double)nc-1);
cvrms=cvrms+(sr-w)*CMath::Sqr(1.0/(double)nc);
}
}
//--- change value
cvrms=MathSqrt(cvrms/(nc*n));
}
}
//--- Calculate threshold.
//--- Code is a bit complicated because there can be such
//--- numbers that 0.5(A+B) equals to A or B (if A-B=epsilon)
threshold=0.5*(a[tiesbuf[koptimal]]+a[tiesbuf[koptimal+1]]);
//--- check
if(threshold<=a[tiesbuf[koptimal]])
threshold=a[tiesbuf[koptimal+1]];
}
//+------------------------------------------------------------------+
//| Automatic non-optimal discretization, internal subroutine. |
//+------------------------------------------------------------------+
static void CBdSS::DSSplitK(double &ca[],int &cc[],const int n,const int nc,
int kmax,int &info,double &thresholds[],int &ni,
double &cve)
{
//--- create variables
int i=0;
int j=0;
int j1=0;
int k=0;
int tiecount=0;
double v2=0;
int bestk=0;
double bestcve=0;
double curcve=0;
//--- creating arrays
int ties[];
int p1[];
int p2[];
int cnt[];
int bestsizes[];
int cursizes[];
double a[];
int c[];
//--- copy
ArrayCopy(a,ca);
ArrayCopy(c,cc);
//--- initialization
info=0;
ni=0;
cve=0;
//--- Test for errors in inputs
if((n<=0 || nc<2) || kmax<2)
{
info=-1;
return;
}
for(i=0;i<=n-1;i++)
{
//--- check
if(c[i]<0 || c[i]>=nc)
{
info=-2;
return;
}
}
//--- change value
info=1;
//--- Tie
DSTie(a,n,ties,tiecount,p1,p2);
//--- swap
for(i=0;i<=n-1;i++)
{
//--- check
if(p2[i]!=i)
{
k=c[i];
c[i]=c[p2[i]];
c[p2[i]]=k;
}
}
//--- Special cases
if(tiecount==1)
{
info=-3;
return;
}
//--- General case:
//--- 0. allocate arrays
kmax=MathMin(kmax,tiecount);
//--- allocation
ArrayResizeAL(bestsizes,kmax);
ArrayResizeAL(cursizes,kmax);
ArrayResizeAL(cnt,nc);
//--- General case:
//--- 1. prepare "weak" solution (two subintervals,divided at median)
v2=CMath::m_maxrealnumber;
j=-1;
for(i=1;i<=tiecount-1;i++)
{
//--- check
if(MathAbs(ties[i]-0.5*(n-1))<v2)
{
v2=MathAbs(ties[i]-0.5*n);
j=i;
}
}
//--- check
if(!CAp::Assert(j>0,__FUNCTION__+": internal error #1!"))
return;
//--- change values
bestk=2;
bestsizes[0]=ties[j];
bestsizes[1]=n-j;
bestcve=0;
//--- calculation
for(i=0;i<=nc-1;i++)
cnt[i]=0;
for(i=0;i<=j-1;i++)
TieAddC(c,ties,i,nc,cnt);
bestcve=bestcve+GetCV(cnt,nc);
//--- calculation
for(i=0;i<=nc-1;i++)
cnt[i]=0;
for(i=j;i<=tiecount-1;i++)
TieAddC(c,ties,i,nc,cnt);
bestcve=bestcve+GetCV(cnt,nc);
//--- General case:
//--- 2. Use greedy algorithm to find sub-optimal split in O(KMax*N) time
for(k=2;k<=kmax;k++)
{
//--- Prepare greedy K-interval split
for(i=0;i<=k-1;i++)
cursizes[i]=0;
//--- change values
i=0;
j=0;
//--- cycle
while(j<=tiecount-1 && i<=k-1)
{
//--- Rule: I-th bin is empty,fill it
if(cursizes[i]==0)
{
cursizes[i]=ties[j+1]-ties[j];
j=j+1;
continue;
}
//--- Rule: (K-1-I) bins left,(K-1-I) ties left (1 tie per bin);next bin
if(tiecount-j==k-1-i)
{
i=i+1;
continue;
}
//--- Rule: last bin,always place in current
if(i==k-1)
{
cursizes[i]=cursizes[i]+ties[j+1]-ties[j];
j=j+1;
continue;
}
//--- Place J-th tie in I-th bin,or leave for I+1-th bin.
if(MathAbs(cursizes[i]+ties[j+1]-ties[j]-(double)n/(double)k)<MathAbs(cursizes[i]-(double)n/(double)k))
{
cursizes[i]=cursizes[i]+ties[j+1]-ties[j];
j=j+1;
}
else
i=i+1;
}
//--- check
if(!CAp::Assert(cursizes[k-1]!=0 && j==tiecount,__FUNCTION__+": internal error #1"))
return;
//--- Calculate CVE
curcve=0;
j=0;
for(i=0;i<=k-1;i++)
{
//--- calculation
for(j1=0;j1<=nc-1;j1++)
cnt[j1]=0;
for(j1=j;j1<=j+cursizes[i]-1;j1++)
cnt[c[j1]]=cnt[c[j1]]+1;
curcve=curcve+GetCV(cnt,nc);
j=j+cursizes[i];
}
//--- Choose best variant
if(curcve<bestcve)
{
for(i=0;i<=k-1;i++)
bestsizes[i]=cursizes[i];
bestcve=curcve;
bestk=k;
}
}
//--- Transform from sizes to thresholds
cve=bestcve;
ni=bestk;
//--- allocation
ArrayResizeAL(thresholds,ni-1);
j=bestsizes[0];
//--- calculation
for(i=1;i<=bestk-1;i++)
{
thresholds[i-1]=0.5*(a[j-1]+a[j]);
j=j+bestsizes[i];
}
}
//+------------------------------------------------------------------+
//| Automatic optimal discretization, internal subroutine. |
//+------------------------------------------------------------------+
static void CBdSS::DSOptimalSplitK(double &ca[],int &cc[],const int n,
const int nc,int kmax,int &info,
double &thresholds[],int &ni,double &cve)
{
//--- create variables
int i=0;
int j=0;
int s=0;
int jl=0;
int jr=0;
double v2=0;
int tiecount=0;
double cvtemp=0;
int k=0;
int koptimal=0;
double cvoptimal=0;
//--- creating arrays
int ties[];
int p1[];
int p2[];
int cnt[];
int cnt2[];
double a[];
int c[];
//--- create matrix
CMatrixDouble cv;
CMatrixInt splits;
//--- copy
ArrayCopy(a,ca);
ArrayCopy(c,cc);
//--- initialization
info=0;
ni=0;
cve=0;
//--- Test for errors in inputs
if((n<=0 || nc<2) || kmax<2)
{
info=-1;
return;
}
for(i=0;i<=n-1;i++)
{
//--- check
if(c[i]<0 || c[i]>=nc)
{
info=-2;
return;
}
}
//--- change value
info=1;
//--- Tie
DSTie(a,n,ties,tiecount,p1,p2);
//--- swap
for(i=0;i<=n-1;i++)
{
//--- check
if(p2[i]!=i)
{
k=c[i];
c[i]=c[p2[i]];
c[p2[i]]=k;
}
}
//--- Special cases
if(tiecount==1)
{
info=-3;
return;
}
//--- General case
//--- Use dynamic programming to find best split in O(KMax*NC*TieCount^2) time
kmax=MathMin(kmax,tiecount);
//--- allocation
cv.Resize(kmax,tiecount);
splits.Resize(kmax,tiecount);
ArrayResizeAL(cnt,nc);
ArrayResizeAL(cnt2,nc);
//--- calculation
for(j=0;j<=nc-1;j++)
cnt[j]=0;
for(j=0;j<=tiecount-1;j++)
{
TieAddC(c,ties,j,nc,cnt);
splits[0].Set(j,0);
cv[0].Set(j,GetCV(cnt,nc));
}
for(k=1;k<=kmax-1;k++)
{
for(j=0;j<=nc-1;j++)
cnt[j]=0;
//--- Subtask size J in [K..TieCount-1]:
//--- optimal K-splitting on ties from 0-th to J-th.
for(j=k;j<=tiecount-1;j++)
{
//--- Update Cnt - let it contain classes of ties from K-th to J-th
TieAddC(c,ties,j,nc,cnt);
//--- Search for optimal split point S in [K..J]
for(i=0;i<=nc-1;i++)
cnt2[i]=cnt[i];
cv[k].Set(j,cv[k-1][j-1]+GetCV(cnt2,nc));
splits[k].Set(j,j);
//--- calculation
for(s=k+1;s<=j;s++)
{
//--- Update Cnt2 - let it contain classes of ties from S-th to J-th
TieSubC(c,ties,s-1,nc,cnt2);
//--- Calculate CVE
cvtemp=cv[k-1][s-1]+GetCV(cnt2,nc);
//--- check
if(cvtemp<cv[k][j])
{
cv[k].Set(j,cvtemp);
splits[k].Set(j,s);
}
}
}
}
//--- Choose best partition,output result
koptimal=-1;
cvoptimal=CMath::m_maxrealnumber;
for(k=0;k<=kmax-1;k++)
{
//--- check
if(cv[k][tiecount-1]<cvoptimal)
{
cvoptimal=cv[k][tiecount-1];
koptimal=k;
}
}
//--- check
if(!CAp::Assert(koptimal>=0,__FUNCTION__+": internal error #1!"))
return;
//--- check
if(koptimal==0)
{
//--- Special case: best partition is one big interval.
//--- Even 2-partition is not better.
//--- This is possible when dealing with "weak" predictor variables.
//--- Make binary split as close to the median as possible.
v2=CMath::m_maxrealnumber;
j=-1;
for(i=1;i<=tiecount-1;i++)
{
//--- check
if(MathAbs(ties[i]-0.5*(n-1))<v2)
{
v2=MathAbs(ties[i]-0.5*(n-1));
j=i;
}
}
//--- check
if(!CAp::Assert(j>0,__FUNCTION__+": internal error #2!"))
return;
//--- allocation
ArrayResizeAL(thresholds,1);
//--- change values
thresholds[0]=0.5*(a[ties[j-1]]+a[ties[j]]);
ni=2;
cve=0;
//--- calculation
for(i=0;i<=nc-1;i++)
cnt[i]=0;
for(i=0;i<=j-1;i++)
TieAddC(c,ties,i,nc,cnt);
cve=cve+GetCV(cnt,nc);
for(i=0;i<=nc-1;i++)
cnt[i]=0;
for(i=j;i<=tiecount-1;i++)
TieAddC(c,ties,i,nc,cnt);
cve=cve+GetCV(cnt,nc);
}
else
{
//--- General case: 2 or more intervals
ArrayResizeAL(thresholds,koptimal);
ni=koptimal+1;
cve=cv[koptimal][tiecount-1];
jl=splits[koptimal][tiecount-1];
jr=tiecount-1;
//--- calculation
for(k=koptimal;k>=1;k--)
{
thresholds[k-1]=0.5*(a[ties[jl-1]]+a[ties[jl]]);
jr=jl-1;
jl=splits[k-1][jl-1];
}
}
}
//+------------------------------------------------------------------+
//| Internal function |
//+------------------------------------------------------------------+
static double CBdSS::XLnY(const double x,const double y)
{
//--- check
if(x==0.0)
return(0);
//--- return result
return(x*MathLog(y));
}
//+------------------------------------------------------------------+
//| Internal function, |
//| returns number of samples of class I in Cnt[I] |
//+------------------------------------------------------------------+
static double CBdSS::GetCV(int &cnt[],const int nc)
{
//--- create variables
double result=0;
int i=0;
double s=0;
//--- calculation
s=0;
for(i=0;i<=nc-1;i++)
s=s+cnt[i];
//--- get result
result=0;
for(i=0;i<=nc-1;i++)
result=result-XLnY(cnt[i],cnt[i]/(s+nc-1));
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Internal function,adds number of samples of class I in tie NTie |
//| to Cnt[I] |
//+------------------------------------------------------------------+
static void CBdSS::TieAddC(int &c[],int &ties[],const int ntie,const int nc,
int &cnt[])
{
//--- create a variable
int i=0;
//--- calculation
for(i=ties[ntie];i<=ties[ntie+1]-1;i++)
cnt[c[i]]=cnt[c[i]]+1;
}
//+------------------------------------------------------------------+
//| Internal function,subtracts number of samples of class I in tie |
//| NTie to Cnt[I] |
//+------------------------------------------------------------------+
static void CBdSS::TieSubC(int &c[],int &ties[],const int ntie,const int nc,
int &cnt[])
{
//--- create a variable
int i=0;
//--- calculation
for(i=ties[ntie];i<=ties[ntie+1]-1;i++)
cnt[c[i]]=cnt[c[i]]-1;
}
//+------------------------------------------------------------------+
//| Auxiliary class for CDForest |
//+------------------------------------------------------------------+
class CDecisionForest
{
public:
int m_nvars;
int m_nclasses;
int m_ntrees;
int m_bufsize;
double m_trees[];
//--- constructor, destructor
CDecisionForest(void);
~CDecisionForest(void);
//--- copy
void Copy(CDecisionForest &obj);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CDecisionForest::CDecisionForest(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CDecisionForest::~CDecisionForest(void)
{
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CDecisionForest::Copy(CDecisionForest &obj)
{
//--- copy variables
m_nvars=obj.m_nvars;
m_nclasses=obj.m_nclasses;
m_ntrees=obj.m_ntrees;
m_bufsize=obj.m_bufsize;
//--- copy array
ArrayCopy(m_trees,obj.m_trees);
}
//+------------------------------------------------------------------+
//| This class is a shell for class CDecisionForest |
//+------------------------------------------------------------------+
class CDecisionForestShell
{
private:
CDecisionForest m_innerobj;
public:
//--- constructors, destructor
CDecisionForestShell(void);
CDecisionForestShell(CDecisionForest &obj);
~CDecisionForestShell(void);
//--- method
CDecisionForest *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CDecisionForestShell::CDecisionForestShell(void)
{
}
//+------------------------------------------------------------------+
//| Copy constructor |
//+------------------------------------------------------------------+
CDecisionForestShell::CDecisionForestShell(CDecisionForest &obj)
{
//--- copy
m_innerobj.Copy(obj);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CDecisionForestShell::~CDecisionForestShell(void)
{
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CDecisionForest *CDecisionForestShell::GetInnerObj(void)
{
//--- return result
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Auxiliary class for CDForest |
//+------------------------------------------------------------------+
class CDFReport
{
public:
//--- variables
double m_relclserror;
double m_avgce;
double m_rmserror;
double m_avgerror;
double m_avgrelerror;
double m_oobrelclserror;
double m_oobavgce;
double m_oobrmserror;
double m_oobavgerror;
double m_oobavgrelerror;
//--- constructor, destructor
CDFReport(void);
~CDFReport(void);
//--- copy
void Copy(CDFReport &obj);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CDFReport::CDFReport(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CDFReport::~CDFReport(void)
{
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CDFReport::Copy(CDFReport &obj)
{
//--- copy variables
m_relclserror=obj.m_relclserror;
m_avgce=obj.m_avgce;
m_rmserror=obj.m_rmserror;
m_avgerror=obj.m_avgerror;
m_avgrelerror=obj.m_avgrelerror;
m_oobrelclserror=obj.m_oobrelclserror;
m_oobavgce=obj.m_oobavgce;
m_oobrmserror=obj.m_oobrmserror;
m_oobavgerror=obj.m_oobavgerror;
m_oobavgrelerror=obj.m_oobavgrelerror;
}
//+------------------------------------------------------------------+
//| This class is a shell for class CDFReport |
//+------------------------------------------------------------------+
class CDFReportShell
{
private:
CDFReport m_innerobj;
public:
//--- constructors, destructor
CDFReportShell(void);
CDFReportShell(CDFReport &obj);
~CDFReportShell(void);
//--- methods
double GetRelClsError(void);
void SetRelClsError(const double d);
double GetAvgCE(void);
void SetAvgCE(const double d);
double GetRMSError(void);
void SetRMSError(const double d);
double GetAvgError(void);
void SetAvgError(const double d);
double GetAvgRelError(void);
void SetAvgRelError(const double d);
double GetOOBRelClsError(void);
void SetOOBRelClsError(const double d);
double GetOOBAvgCE(void);
void SetOOBAvgCE(const double d);
double GetOOBRMSError(void);
void SetOOBRMSError(const double d);
double GetOOBAvgError(void);
void SetOOBAvgError(const double d);
double GetOOBAvgRelError(void);
void SetOOBAvgRelError(const double d);
CDFReport *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CDFReportShell::CDFReportShell(void)
{
}
//+------------------------------------------------------------------+
//| Copy constructor |
//+------------------------------------------------------------------+
CDFReportShell::CDFReportShell(CDFReport &obj)
{
//--- copy
m_innerobj.Copy(obj);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CDFReportShell::~CDFReportShell(void)
{
}
//+------------------------------------------------------------------+
//| Returns the value of the variable relclserror |
//+------------------------------------------------------------------+
double CDFReportShell::GetRelClsError(void)
{
//--- return result
return(m_innerobj.m_relclserror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable relclserror |
//+------------------------------------------------------------------+
void CDFReportShell::SetRelClsError(const double d)
{
//--- change value
m_innerobj.m_relclserror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgce |
//+------------------------------------------------------------------+
double CDFReportShell::GetAvgCE(void)
{
//--- return result
return(m_innerobj.m_avgce);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgce |
//+------------------------------------------------------------------+
void CDFReportShell::SetAvgCE(const double d)
{
//--- change value
m_innerobj.m_avgce=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable rmserror |
//+------------------------------------------------------------------+
double CDFReportShell::GetRMSError(void)
{
//--- return result
return(m_innerobj.m_rmserror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable rmserror |
//+------------------------------------------------------------------+
void CDFReportShell::SetRMSError(const double d)
{
//--- change value
m_innerobj.m_rmserror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgerror |
//+------------------------------------------------------------------+
double CDFReportShell::GetAvgError(void)
{
//--- return result
return(m_innerobj.m_avgerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgerror |
//+------------------------------------------------------------------+
void CDFReportShell::SetAvgError(const double d)
{
//--- change value
m_innerobj.m_avgerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgrelerror |
//+------------------------------------------------------------------+
double CDFReportShell::GetAvgRelError(void)
{
//--- return result
return(m_innerobj.m_avgrelerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgrelerror |
//+------------------------------------------------------------------+
void CDFReportShell::SetAvgRelError(const double d)
{
//--- change value
m_innerobj.m_avgrelerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable oobrelclserror |
//+------------------------------------------------------------------+
double CDFReportShell::GetOOBRelClsError(void)
{
//--- return result
return(m_innerobj.m_oobrelclserror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable oobrelclserror |
//+------------------------------------------------------------------+
void CDFReportShell::SetOOBRelClsError(const double d)
{
//--- change value
m_innerobj.m_oobrelclserror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable oobavgce |
//+------------------------------------------------------------------+
double CDFReportShell::GetOOBAvgCE(void)
{
//--- return result
return(m_innerobj.m_oobavgce);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable oobavgce |
//+------------------------------------------------------------------+
void CDFReportShell::SetOOBAvgCE(const double d)
{
//--- change value
m_innerobj.m_oobavgce=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable oobrmserror |
//+------------------------------------------------------------------+
double CDFReportShell::GetOOBRMSError(void)
{
//--- return result
return(m_innerobj.m_oobrmserror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable oobrmserror |
//+------------------------------------------------------------------+
void CDFReportShell::SetOOBRMSError(const double d)
{
//--- change value
m_innerobj.m_oobrmserror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable oobavgerror |
//+------------------------------------------------------------------+
double CDFReportShell::GetOOBAvgError(void)
{
//--- return result
return(m_innerobj.m_oobavgerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable oobavgerror |
//+------------------------------------------------------------------+
void CDFReportShell::SetOOBAvgError(const double d)
{
//--- change value
m_innerobj.m_oobavgerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable oobavgrelerror |
//+------------------------------------------------------------------+
double CDFReportShell::GetOOBAvgRelError(void)
{
//--- return result
return(m_innerobj.m_oobavgrelerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable oobavgrelerror |
//+------------------------------------------------------------------+
void CDFReportShell::SetOOBAvgRelError(const double d)
{
//--- change value
m_innerobj.m_oobavgrelerror=d;
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CDFReport *CDFReportShell::GetInnerObj(void)
{
//--- return result
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Auxiliary class for CDForest |
//+------------------------------------------------------------------+
class CDFInternalBuffers
{
public:
//--- arrays
double m_treebuf[];
int m_idxbuf[];
double m_tmpbufr[];
double m_tmpbufr2[];
int m_tmpbufi[];
int m_classibuf[];
double m_sortrbuf[];
double m_sortrbuf2[];
int m_sortibuf[];
int m_varpool[];
bool m_evsbin[];
double m_evssplits[];
//--- constructor, destructor
CDFInternalBuffers(void);
~CDFInternalBuffers(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CDFInternalBuffers::CDFInternalBuffers(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CDFInternalBuffers::~CDFInternalBuffers(void)
{
}
//+------------------------------------------------------------------+
//| Decision forest class |
//+------------------------------------------------------------------+
class CDForest
{
private:
//--- private methods
static int DFClsError(CDecisionForest &df,CMatrixDouble &xy,const int npoints);
static void DFProcessInternal(CDecisionForest &df,const int offs,double &x[],double &y[]);
static void DFBuildTree(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,const int nfeatures,const int nvarsinpool,const int flags,CDFInternalBuffers &bufs);
static void DFBuildTreeRec(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,const int nfeatures,int nvarsinpool,const int flags,int &numprocessed,const int idx1,const int idx2,CDFInternalBuffers &bufs);
static void DFSplitC(double &x[],int &c[],int &cntbuf[],const int n,const int nc,const int flags,int &info,double &threshold,double &e,double &sortrbuf[],int &sortibuf[]);
static void DFSplitR(double &x[],double &y[],const int n,const int flags,int &info,double &threshold,double &e,double &sortrbuf[],double &sortrbuf2[]);
public:
//--- class constants
static const int m_innernodewidth;
static const int m_leafnodewidth;
static const int m_dfusestrongsplits;
static const int m_dfuseevs;
static const int m_dffirstversion;
//--- constructor, destructor
CDForest(void);
~CDForest(void);
//--- public methods
static void DFBuildRandomDecisionForest(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,const int ntrees,const double r,int &info,CDecisionForest &df,CDFReport &rep);
static void DFBuildRandomDecisionForestX1(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,const int ntrees,const int nrndvars,const double r,int &info,CDecisionForest &df,CDFReport &rep);
static void DFBuildInternal(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,const int ntrees,const int samplesize,const int nfeatures,const int flags,int &info,CDecisionForest &df,CDFReport &rep);
static void DFProcess(CDecisionForest &df,double &x[],double &y[]);
static void DFProcessI(CDecisionForest &df,double &x[],double &y[]);
static double DFRelClsError(CDecisionForest &df,CMatrixDouble &xy,const int npoints);
static double DFAvgCE(CDecisionForest &df,CMatrixDouble &xy,const int npoints);
static double DFRMSError(CDecisionForest &df,CMatrixDouble &xy,const int npoints);
static double DFAvgError(CDecisionForest &df,CMatrixDouble &xy,const int npoints);
static double DFAvgRelError(CDecisionForest &df,CMatrixDouble &xy,const int npoints);
static void DFCopy(CDecisionForest &df1,CDecisionForest &df2);
static void DFAlloc(CSerializer &s,CDecisionForest &forest);
static void DFSerialize(CSerializer &s,CDecisionForest &forest);
static void DFUnserialize(CSerializer &s,CDecisionForest &forest);
};
//+------------------------------------------------------------------+
//| Initialize constants |
//+------------------------------------------------------------------+
const int CDForest::m_innernodewidth=3;
const int CDForest::m_leafnodewidth=2;
const int CDForest::m_dfusestrongsplits=1;
const int CDForest::m_dfuseevs=2;
const int CDForest::m_dffirstversion=0;
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CDForest::CDForest(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CDForest::~CDForest(void)
{
}
//+------------------------------------------------------------------+
//| This subroutine builds random decision forest. |
//| INPUT PARAMETERS: |
//| XY - training set |
//| NPoints - training set size, NPoints>=1 |
//| NVars - number of independent variables, NVars>=1 |
//| NClasses - task type: |
//| * NClasses=1 - regression task with one |
//| dependent variable |
//| * NClasses>1 - classification task with |
//| NClasses classes. |
//| NTrees - number of trees in a forest, NTrees>=1. |
//| recommended values: 50-100. |
//| R - percent of a training set used to build |
//| individual trees. 0<R<=1. |
//| recommended values: 0.1 <= R <= 0.66. |
//| OUTPUT PARAMETERS: |
//| Info - return code: |
//| * -2, if there is a point with class number |
//| outside of [0..NClasses-1]. |
//| * -1, if incorrect parameters was passed |
//| (NPoints<1, NVars<1, NClasses<1, |
//| NTrees<1, R<=0 or R>1). |
//| * 1, if task has been solved |
//| DF - model built |
//| Rep - training report, contains error on a training|
//| set and out-of-bag estimates of |
//| generalization error. |
//+------------------------------------------------------------------+
static void CDForest::DFBuildRandomDecisionForest(CMatrixDouble &xy,
const int npoints,
const int nvars,
const int nclasses,
const int ntrees,
const double r,int &info,
CDecisionForest &df,
CDFReport &rep)
{
//--- create a variable
int samplesize=0;
//--- initialization
info=0;
//--- check
if(r<=0.0 || r>1.0)
{
info=-1;
return;
}
//--- calculation
samplesize=(int)(MathMax((int)MathRound(r*npoints),1));
//--- function call
DFBuildInternal(xy,npoints,nvars,nclasses,ntrees,samplesize,(int)(MathMax(nvars/2,1)),m_dfusestrongsplits+m_dfuseevs,info,df,rep);
}
//+------------------------------------------------------------------+
//| This subroutine builds random decision forest. |
//| This function gives ability to tune number of variables used when|
//| choosing best split. |
//| INPUT PARAMETERS: |
//| XY - training set |
//| NPoints - training set size, NPoints>=1 |
//| NVars - number of independent variables, NVars>=1 |
//| NClasses - task type: |
//| * NClasses=1 - regression task with one |
//| dependent variable |
//| * NClasses>1 - classification task with |
//| NClasses classes. |
//| NTrees - number of trees in a forest, NTrees>=1. |
//| recommended values: 50-100. |
//| NRndVars - number of variables used when choosing best |
//| split |
//| R - percent of a training set used to build |
//| individual trees. 0<R<=1. |
//| recommended values: 0.1 <= R <= 0.66. |
//| OUTPUT PARAMETERS: |
//| Info - return code: |
//| * -2, if there is a point with class number |
//| outside of [0..NClasses-1]. |
//| * -1, if incorrect parameters was passed |
//| (NPoints<1, NVars<1, NClasses<1, |
//| NTrees<1, R<=0 or R>1). |
//| * 1, if task has been solved |
//| DF - model built |
//| Rep - training report, contains error on a training|
//| set and out-of-bag estimates of |
//| generalization error. |
//+------------------------------------------------------------------+
static void CDForest::DFBuildRandomDecisionForestX1(CMatrixDouble &xy,
const int npoints,
const int nvars,
const int nclasses,
const int ntrees,
const int nrndvars,
const double r,int &info,
CDecisionForest &df,
CDFReport &rep)
{
//--- create a variable
int samplesize=0;
//--- initialization
info=0;
//--- check
if(r<=0.0 || r>1.0)
{
info=-1;
return;
}
//--- check
if(nrndvars<=0 || nrndvars>nvars)
{
info=-1;
return;
}
//--- calculation
samplesize=(int)(MathMax((int)MathRound(r*npoints),1));
//--- function call
DFBuildInternal(xy,npoints,nvars,nclasses,ntrees,samplesize,nrndvars,m_dfusestrongsplits+m_dfuseevs,info,df,rep);
}
//+------------------------------------------------------------------+
//| Class method |
//+------------------------------------------------------------------+
static void CDForest::DFBuildInternal(CMatrixDouble &xy,const int npoints,
const int nvars,const int nclasses,
const int ntrees,const int samplesize,
const int nfeatures,const int flags,
int &info,CDecisionForest &df,
CDFReport &rep)
{
//--- create variables
int i=0;
int j=0;
int k=0;
int tmpi=0;
int lasttreeoffs=0;
int offs=0;
int ooboffs=0;
int treesize=0;
int nvarsinpool=0;
bool useevs;
int oobcnt=0;
int oobrelcnt=0;
double v=0;
double vmin=0;
double vmax=0;
bool bflag;
int i_=0;
int i1_=0;
//--- creating arrays
int permbuf[];
double oobbuf[];
int oobcntbuf[];
double x[];
double y[];
//--- create matrix
CMatrixDouble xys;
//--- create object of class
CDFInternalBuffers bufs;
//--- initialization
info=0;
//--- Test for inputs
if(npoints<1 || samplesize<1 || samplesize>npoints || nvars<1 || nclasses<1 || ntrees<1 || nfeatures<1)
{
info=-1;
return;
}
//--- check
if(nclasses>1)
{
for(i=0;i<=npoints-1;i++)
{
//--- check
if((int)MathRound(xy[i][nvars])<0 || (int)MathRound(xy[i][nvars])>=nclasses)
{
info=-2;
return;
}
}
}
//--- change value
info=1;
//--- Flags
useevs=flags/m_dfuseevs%2!=0;
//--- Allocate data,prepare header
treesize=1+m_innernodewidth*(samplesize-1)+m_leafnodewidth*samplesize;
//--- allocation
ArrayResizeAL(permbuf,npoints);
ArrayResizeAL(bufs.m_treebuf,treesize);
ArrayResizeAL(bufs.m_idxbuf,npoints);
ArrayResizeAL(bufs.m_tmpbufr,npoints);
ArrayResizeAL(bufs.m_tmpbufr2,npoints);
ArrayResizeAL(bufs.m_tmpbufi,npoints);
ArrayResizeAL(bufs.m_sortrbuf,npoints);
ArrayResizeAL(bufs.m_sortrbuf2,npoints);
ArrayResizeAL(bufs.m_sortibuf,npoints);
ArrayResizeAL(bufs.m_varpool,nvars);
ArrayResizeAL(bufs.m_evsbin,nvars);
ArrayResizeAL(bufs.m_evssplits,nvars);
ArrayResizeAL(bufs.m_classibuf,2*nclasses);
ArrayResizeAL(oobbuf,nclasses*npoints);
ArrayResizeAL(oobcntbuf,npoints);
ArrayResizeAL(df.m_trees,ntrees*treesize);
xys.Resize(samplesize,nvars+1);
ArrayResizeAL(x,nvars);
ArrayResizeAL(y,nclasses);
//--- initialization
for(i=0;i<=npoints-1;i++)
permbuf[i]=i;
for(i=0;i<=npoints*nclasses-1;i++)
oobbuf[i]=0;
for(i=0;i<=npoints-1;i++)
oobcntbuf[i]=0;
//--- Prepare variable pool and EVS (extended variable selection/splitting) buffers
//--- (whether EVS is turned on or not):
//--- 1. detect binary variables and pre-calculate splits for them
//--- 2. detect variables with non-distinct values and exclude them from pool
for(i=0;i<=nvars-1;i++)
bufs.m_varpool[i]=i;
nvarsinpool=nvars;
//--- check
if(useevs)
{
for(j=0;j<=nvars-1;j++)
{
vmin=xy[0][j];
vmax=vmin;
//--- calculation
for(i=0;i<=npoints-1;i++)
{
v=xy[i][j];
vmin=MathMin(vmin,v);
vmax=MathMax(vmax,v);
}
//--- check
if(vmin==vmax)
{
//--- exclude variable from pool
bufs.m_varpool[j]=bufs.m_varpool[nvarsinpool-1];
bufs.m_varpool[nvarsinpool-1]=-1;
nvarsinpool=nvarsinpool-1;
continue;
}
//--- change value
bflag=false;
for(i=0;i<=npoints-1;i++)
{
v=xy[i][j];
//--- check
if(v!=vmin&&v!=vmax)
{
bflag=true;
break;
}
}
//--- check
if(bflag)
{
//--- non-binary variable
bufs.m_evsbin[j]=false;
}
else
{
//--- Prepare
bufs.m_evsbin[j]=true;
bufs.m_evssplits[j]=0.5*(vmin+vmax);
//--- check
if(bufs.m_evssplits[j]<=vmin)
bufs.m_evssplits[j]=vmax;
}
}
}
//--- RANDOM FOREST FORMAT
//--- W[0] - size of array
//--- W[1] - version number
//--- W[2] - NVars
//--- W[3] - NClasses (1 for regression)
//--- W[4] - NTrees
//--- W[5] - trees offset
//--- TREE FORMAT
//--- W[Offs] - size of sub-array
//--- node info:
//--- W[K+0] - variable number (-1 for leaf mode)
//--- W[K+1] - threshold (class/value for leaf node)
//--- W[K+2] - ">=" branch index (absent for leaf node)
df.m_nvars=nvars;
df.m_nclasses=nclasses;
df.m_ntrees=ntrees;
//--- Build forest
offs=0;
for(i=0;i<=ntrees-1;i++)
{
//--- Prepare sample
for(k=0;k<=samplesize-1;k++)
{
//--- calculation
j=k+CMath::RandomInteger(npoints-k);
tmpi=permbuf[k];
permbuf[k]=permbuf[j];
permbuf[j]=tmpi;
j=permbuf[k];
for(i_=0;i_<=nvars;i_++)
xys[k].Set(i_,xy[j][i_]);
}
//--- build tree,copy
DFBuildTree(xys,samplesize,nvars,nclasses,nfeatures,nvarsinpool,flags,bufs);
//--- calculation
j=(int)MathRound(bufs.m_treebuf[0]);
i1_=-offs;
for(i_=offs;i_<=offs+j-1;i_++)
df.m_trees[i_]=bufs.m_treebuf[i_+i1_];
lasttreeoffs=offs;
offs=offs+j;
//--- OOB estimates
for(k=samplesize;k<=npoints-1;k++)
{
for(j=0;j<=nclasses-1;j++)
y[j]=0;
j=permbuf[k];
for(i_=0;i_<=nvars-1;i_++)
x[i_]=xy[j][i_];
//--- function call
DFProcessInternal(df,lasttreeoffs,x,y);
//--- calculation
i1_=-j*nclasses;
for(i_=j*nclasses;i_<=(j+1)*nclasses-1;i_++)
oobbuf[i_]=oobbuf[i_]+y[i_+i1_];
oobcntbuf[j]=oobcntbuf[j]+1;
}
}
df.m_bufsize=offs;
//--- Normalize OOB results
for(i=0;i<=npoints-1;i++)
{
//--- check
if(oobcntbuf[i]!=0)
{
v=1.0/(double)oobcntbuf[i];
for(i_=i*nclasses;i_<=i*nclasses+nclasses-1;i_++)
oobbuf[i_]=v*oobbuf[i_];
}
}
//--- Calculate training set estimates
rep.m_relclserror=DFRelClsError(df,xy,npoints);
rep.m_avgce=DFAvgCE(df,xy,npoints);
rep.m_rmserror=DFRMSError(df,xy,npoints);
rep.m_avgerror=DFAvgError(df,xy,npoints);
rep.m_avgrelerror=DFAvgRelError(df,xy,npoints);
//--- Calculate OOB estimates.
rep.m_oobrelclserror=0;
rep.m_oobavgce=0;
rep.m_oobrmserror=0;
rep.m_oobavgerror=0;
rep.m_oobavgrelerror=0;
oobcnt=0;
oobrelcnt=0;
for(i=0;i<=npoints-1;i++)
{
//--- check
if(oobcntbuf[i]!=0)
{
ooboffs=i*nclasses;
//--- check
if(nclasses>1)
{
//--- classification-specific code
k=(int)MathRound(xy[i][nvars]);
tmpi=0;
for(j=1;j<=nclasses-1;j++)
{
//--- check
if(oobbuf[ooboffs+j]>oobbuf[ooboffs+tmpi])
tmpi=j;
}
//--- check
if(tmpi!=k)
rep.m_oobrelclserror=rep.m_oobrelclserror+1;
//--- check
if(oobbuf[ooboffs+k]!=0.0)
rep.m_oobavgce=rep.m_oobavgce-MathLog(oobbuf[ooboffs+k]);
else
rep.m_oobavgce=rep.m_oobavgce-MathLog(CMath::m_minrealnumber);
//--- calculation
for(j=0;j<=nclasses-1;j++)
{
//--- check
if(j==k)
{
rep.m_oobrmserror=rep.m_oobrmserror+CMath::Sqr(oobbuf[ooboffs+j]-1);
rep.m_oobavgerror=rep.m_oobavgerror+MathAbs(oobbuf[ooboffs+j]-1);
rep.m_oobavgrelerror=rep.m_oobavgrelerror+MathAbs(oobbuf[ooboffs+j]-1);
oobrelcnt=oobrelcnt+1;
}
else
{
rep.m_oobrmserror=rep.m_oobrmserror+CMath::Sqr(oobbuf[ooboffs+j]);
rep.m_oobavgerror=rep.m_oobavgerror+MathAbs(oobbuf[ooboffs+j]);
}
}
}
else
{
//--- regression-specific code
rep.m_oobrmserror=rep.m_oobrmserror+CMath::Sqr(oobbuf[ooboffs]-xy[i][nvars]);
rep.m_oobavgerror=rep.m_oobavgerror+MathAbs(oobbuf[ooboffs]-xy[i][nvars]);
//--- check
if(xy[i][nvars]!=0.0)
{
rep.m_oobavgrelerror=rep.m_oobavgrelerror+MathAbs((oobbuf[ooboffs]-xy[i][nvars])/xy[i][nvars]);
oobrelcnt=oobrelcnt+1;
}
}
//--- update OOB estimates count.
oobcnt=oobcnt+1;
}
}
//--- check
if(oobcnt>0)
{
//--- change values
rep.m_oobrelclserror=rep.m_oobrelclserror/oobcnt;
rep.m_oobavgce=rep.m_oobavgce/oobcnt;
rep.m_oobrmserror=MathSqrt(rep.m_oobrmserror/(oobcnt*nclasses));
rep.m_oobavgerror=rep.m_oobavgerror/(oobcnt*nclasses);
//--- check
if(oobrelcnt>0)
rep.m_oobavgrelerror=rep.m_oobavgrelerror/oobrelcnt;
}
}
//+------------------------------------------------------------------+
//| Procesing |
//| INPUT PARAMETERS: |
//| DF - decision forest model |
//| X - input vector, array[0..NVars-1]. |
//| OUTPUT PARAMETERS: |
//| Y - result. Regression estimate when solving |
//| regression task, vector of posterior |
//| probabilities for classification task. |
//| See also DFProcessI. |
//+------------------------------------------------------------------+
static void CDForest::DFProcess(CDecisionForest &df,double &x[],double &y[])
{
//--- create variables
int offs=0;
int i=0;
double v=0;
int i_=0;
//--- Proceed
if(CAp::Len(y)<df.m_nclasses)
ArrayResizeAL(y,df.m_nclasses);
//--- initialization
offs=0;
for(i=0;i<=df.m_nclasses-1;i++)
y[i]=0;
for(i=0;i<=df.m_ntrees-1;i++)
{
//--- Process basic tree
DFProcessInternal(df,offs,x,y);
//--- Next tree
offs=offs+(int)MathRound(df.m_trees[offs]);
}
//--- calculation
v=1.0/(double)df.m_ntrees;
for(i_=0;i_<=df.m_nclasses-1;i_++)
y[i_]=v*y[i_];
}
//+------------------------------------------------------------------+
//| 'interactive' variant of DFProcess for languages like Python |
//| which support constructs like "Y = DFProcessI(DF,X)" and |
//| interactive mode of interpreter |
//| This function allocates new array on each call, so it is |
//| significantly slower than its 'non-interactive' counterpart, but |
//| it is more convenient when you call it from command line. |
//+------------------------------------------------------------------+
static void CDForest::DFProcessI(CDecisionForest &df,double &x[],double &y[])
{
//--- function call
DFProcess(df,x,y);
}
//+------------------------------------------------------------------+
//| Relative classification error on the test set |
//| INPUT PARAMETERS: |
//| DF - decision forest model |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| percent of incorrectly classified cases. |
//| Zero if model solves regression task. |
//+------------------------------------------------------------------+
static double CDForest::DFRelClsError(CDecisionForest &df,CMatrixDouble &xy,
const int npoints)
{
//--- return result
return((double)DFClsError(df,xy,npoints)/(double)npoints);
}
//+------------------------------------------------------------------+
//| Average cross-entropy (in bits per element) on the test set |
//| INPUT PARAMETERS: |
//| DF - decision forest model |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| CrossEntropy/(NPoints*LN(2)). |
//| Zero if model solves regression task. |
//+------------------------------------------------------------------+
static double CDForest::DFAvgCE(CDecisionForest &df,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double result=0;
int i=0;
int j=0;
int k=0;
int tmpi=0;
int i_=0;
//--- creating arrays
double x[];
double y[];
//--- allocation
ArrayResizeAL(x,df.m_nvars);
ArrayResizeAL(y,df.m_nclasses);
//--- initialization
result=0;
for(i=0;i<=npoints-1;i++)
{
for(i_=0;i_<=df.m_nvars-1;i_++)
x[i_]=xy[i][i_];
//--- function call
DFProcess(df,x,y);
//--- check
if(df.m_nclasses>1)
{
//--- classification-specific code
k=(int)MathRound(xy[i][df.m_nvars]);
tmpi=0;
for(j=1;j<=df.m_nclasses-1;j++)
{
//--- check
if(y[j]>(double)(y[tmpi]))
tmpi=j;
}
//--- check
if(y[k]!=0.0)
result=result-MathLog(y[k]);
else
result=result-MathLog(CMath::m_minrealnumber);
}
}
//--- return result
return(result/npoints);
}
//+------------------------------------------------------------------+
//| RMS error on the test set |
//| INPUT PARAMETERS: |
//| DF - decision forest model |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| root mean square error. |
//| Its meaning for regression task is obvious. As for |
//| classification task,RMS error means error when estimating |
//| posterior probabilities. |
//+------------------------------------------------------------------+
static double CDForest::DFRMSError(CDecisionForest &df,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double result=0;
int i=0;
int j=0;
int k=0;
int tmpi=0;
int i_=0;
//--- creating arrays
double x[];
double y[];
//--- allocation
ArrayResizeAL(x,df.m_nvars);
ArrayResizeAL(y,df.m_nclasses);
//--- initialization
result=0;
for(i=0;i<=npoints-1;i++)
{
for(i_=0;i_<=df.m_nvars-1;i_++)
x[i_]=xy[i][i_];
//--- function call
DFProcess(df,x,y);
//--- check
if(df.m_nclasses>1)
{
//--- classification-specific code
k=(int)MathRound(xy[i][df.m_nvars]);
tmpi=0;
for(j=1;j<=df.m_nclasses-1;j++)
{
//--- check
if(y[j]>y[tmpi])
tmpi=j;
}
for(j=0;j<=df.m_nclasses-1;j++)
{
//--- check
if(j==k)
result=result+CMath::Sqr(y[j]-1);
else
result=result+CMath::Sqr(y[j]);
}
}
else
{
//--- regression-specific code
result=result+CMath::Sqr(y[0]-xy[i][df.m_nvars]);
}
}
//--- return result
return(MathSqrt(result/(npoints*df.m_nclasses)));
}
//+------------------------------------------------------------------+
//| Average error on the test set |
//| INPUT PARAMETERS: |
//| DF - decision forest model |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| Its meaning for regression task is obvious. As for |
//| classification task, it means average error when estimating |
//| posterior probabilities. |
//+------------------------------------------------------------------+
static double CDForest::DFAvgError(CDecisionForest &df,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double result=0;
int i=0;
int j=0;
int k=0;
int i_=0;
//--- creating arrays
double x[];
double y[];
//--- allocation
ArrayResizeAL(x,df.m_nvars);
ArrayResizeAL(y,df.m_nclasses);
//--- initialization
result=0;
for(i=0;i<=npoints-1;i++)
{
//--- copy
for(i_=0;i_<=df.m_nvars-1;i_++)
x[i_]=xy[i][i_];
//--- function call
DFProcess(df,x,y);
//--- check
if(df.m_nclasses>1)
{
//--- classification-specific code
k=(int)MathRound(xy[i][df.m_nvars]);
for(j=0;j<=df.m_nclasses-1;j++)
{
//--- check
if(j==k)
result=result+MathAbs(y[j]-1);
else
result=result+MathAbs(y[j]);
}
}
else
{
//--- regression-specific code
result=result+MathAbs(y[0]-xy[i][df.m_nvars]);
}
}
//--- return result
return(result/(npoints*df.m_nclasses));
}
//+------------------------------------------------------------------+
//| Average relative error on the test set |
//| INPUT PARAMETERS: |
//| DF - decision forest model |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| Its meaning for regression task is obvious. As for |
//| classification task, it means average relative error when |
//| estimating posterior probability of belonging to the correct |
//| class. |
//+------------------------------------------------------------------+
static double CDForest::DFAvgRelError(CDecisionForest &df,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double result=0;
int relcnt=0;
int i=0;
int j=0;
int k=0;
int i_=0;
//--- creating arrays
double x[];
double y[];
//--- allocation
ArrayResizeAL(x,df.m_nvars);
ArrayResizeAL(y,df.m_nclasses);
//--- initialization
result=0;
relcnt=0;
for(i=0;i<=npoints-1;i++)
{
//--- copy
for(i_=0;i_<=df.m_nvars-1;i_++)
x[i_]=xy[i][i_];
//--- function call
DFProcess(df,x,y);
//--- check
if(df.m_nclasses>1)
{
//--- classification-specific code
k=(int)MathRound(xy[i][df.m_nvars]);
for(j=0;j<=df.m_nclasses-1;j++)
{
//--- check
if(j==k)
{
result=result+MathAbs(y[j]-1);
relcnt=relcnt+1;
}
}
}
else
{
//--- regression-specific code
if(xy[i][df.m_nvars]!=0.0)
{
result=result+MathAbs((y[0]-xy[i][df.m_nvars])/xy[i][df.m_nvars]);
relcnt=relcnt+1;
}
}
}
//--- check
if(relcnt>0)
result=result/relcnt;
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Copying of DecisionForest strucure |
//| INPUT PARAMETERS: |
//| DF1 - original |
//| OUTPUT PARAMETERS: |
//| DF2 - copy |
//+------------------------------------------------------------------+
static void CDForest::DFCopy(CDecisionForest &df1,CDecisionForest &df2)
{
//--- create a variable
int i_=0;
//--- change values
df2.m_nvars=df1.m_nvars;
df2.m_nclasses=df1.m_nclasses;
df2.m_ntrees=df1.m_ntrees;
df2.m_bufsize=df1.m_bufsize;
//--- allocation
ArrayResizeAL(df2.m_trees,df1.m_bufsize);
//--- copy
for(i_=0;i_<=df1.m_bufsize-1;i_++)
df2.m_trees[i_]=df1.m_trees[i_];
}
//+------------------------------------------------------------------+
//| Serializer: allocation |
//+------------------------------------------------------------------+
static void CDForest::DFAlloc(CSerializer &s,CDecisionForest &forest)
{
//--- preparation to serialize
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
//--- function call
CApServ::AllocRealArray(s,forest.m_trees,forest.m_bufsize);
}
//+------------------------------------------------------------------+
//| Serializer: serialization |
//+------------------------------------------------------------------+
static void CDForest::DFSerialize(CSerializer &s,CDecisionForest &forest)
{
//--- serializetion
s.Serialize_Int(CSCodes::GetRDFSerializationCode());
s.Serialize_Int(m_dffirstversion);
s.Serialize_Int(forest.m_nvars);
s.Serialize_Int(forest.m_nclasses);
s.Serialize_Int(forest.m_ntrees);
s.Serialize_Int(forest.m_bufsize);
//--- function call
CApServ::SerializeRealArray(s,forest.m_trees,forest.m_bufsize);
}
//+------------------------------------------------------------------+
//| Serializer: unserialization |
//+------------------------------------------------------------------+
static void CDForest::DFUnserialize(CSerializer &s,CDecisionForest &forest)
{
//--- create variables
int i0=0;
int i1=0;
//--- check correctness of header
i0=s.Unserialize_Int();
//--- check
if(!CAp::Assert(i0==CSCodes::GetRDFSerializationCode(),__FUNCTION__+": stream header corrupted"))
return;
//--- unserializetion
i1=s.Unserialize_Int();
//--- check
if(!CAp::Assert(i1==m_dffirstversion,__FUNCTION__+": stream header corrupted"))
return;
//--- Unserialize data
forest.m_nvars=s.Unserialize_Int();
forest.m_nclasses=s.Unserialize_Int();
forest.m_ntrees=s.Unserialize_Int();
forest.m_bufsize=s.Unserialize_Int();
//--- function call
CApServ::UnserializeRealArray(s,forest.m_trees);
}
//+------------------------------------------------------------------+
//| Classification error |
//+------------------------------------------------------------------+
static int CDForest::DFClsError(CDecisionForest &df,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
int result=0;
int i=0;
int j=0;
int k=0;
int tmpi=0;
int i_=0;
//--- creating arrays
double x[];
double y[];
//--- check
if(df.m_nclasses<=1)
return(0);
//--- allocation
ArrayResizeAL(x,df.m_nvars);
ArrayResizeAL(y,df.m_nclasses);
//--- initialization
result=0;
for(i=0;i<=npoints-1;i++)
{
//--- copy
for(i_=0;i_<=df.m_nvars-1;i_++)
x[i_]=xy[i][i_];
//--- function call
DFProcess(df,x,y);
//--- change values
k=(int)MathRound(xy[i][df.m_nvars]);
tmpi=0;
for(j=1;j<=df.m_nclasses-1;j++)
{
//--- check
if(y[j]>(double)(y[tmpi]))
tmpi=j;
}
//--- check
if(tmpi!=k)
result=result+1;
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Internal subroutine for processing one decision tree starting at |
//| Offs |
//+------------------------------------------------------------------+
static void CDForest::DFProcessInternal(CDecisionForest &df,const int offs,
double &x[],double &y[])
{
//--- create variables
int k=0;
int idx=0;
//--- Set pointer to the root
k=offs+1;
//--- Navigate through the tree
while(true)
{
//--- check
if(df.m_trees[k]==-1.0)
{
//--- check
if(df.m_nclasses==1)
y[0]=y[0]+df.m_trees[k+1];
else
{
idx=(int)MathRound(df.m_trees[k+1]);
y[idx]=y[idx]+1;
}
//--- break the cycle
break;
}
//--- check
if(x[(int)MathRound(df.m_trees[k])]<df.m_trees[k+1])
k=k+m_innernodewidth;
else
k=offs+(int)MathRound(df.m_trees[k+2]);
}
}
//+------------------------------------------------------------------+
//| Builds one decision tree. Just a wrapper for the DFBuildTreeRec. |
//+------------------------------------------------------------------+
static void CDForest::DFBuildTree(CMatrixDouble &xy,const int npoints,
const int nvars,const int nclasses,
const int nfeatures,const int nvarsinpool,
const int flags,CDFInternalBuffers &bufs)
{
//--- create variables
int numprocessed=0;
int i=0;
//--- check
if(!CAp::Assert(npoints>0))
return;
//--- Prepare IdxBuf. It stores indices of the training set elements.
//--- When training set is being split,contents of IdxBuf is
//--- correspondingly reordered so we can know which elements belong
//--- to which branch of decision tree.
for(i=0;i<=npoints-1;i++)
bufs.m_idxbuf[i]=i;
//--- Recursive procedure
numprocessed=1;
//--- function call
DFBuildTreeRec(xy,npoints,nvars,nclasses,nfeatures,nvarsinpool,flags,numprocessed,0,npoints-1,bufs);
//--- change values
bufs.m_treebuf[0]=numprocessed;
}
//+------------------------------------------------------------------+
//| Builds one decision tree (internal recursive subroutine) |
//| Parameters: |
//| TreeBuf - large enough array,at least TreeSize |
//| IdxBuf - at least NPoints elements |
//| TmpBufR - at least NPoints |
//| TmpBufR2 - at least NPoints |
//| TmpBufI - at least NPoints |
//| TmpBufI2 - at least NPoints+1 |
//+------------------------------------------------------------------+
static void CDForest::DFBuildTreeRec(CMatrixDouble &xy,const int npoints,
const int nvars,const int nclasses,
const int nfeatures,int nvarsinpool,
const int flags,int &numprocessed,
const int idx1,const int idx2,
CDFInternalBuffers &bufs)
{
//--- create variables
int i=0;
int j=0;
int k=0;
bool bflag;
int i1=0;
int i2=0;
int info=0;
double sl=0;
double sr=0;
double w=0;
int idxbest=0;
double ebest=0;
double tbest=0;
int varcur=0;
double s=0;
double v=0;
double v1=0;
double v2=0;
double threshold=0;
int oldnp=0;
double currms=0;
bool useevs;
//--- these initializers are not really necessary,
//--- but without them compiler complains about uninitialized locals
tbest=0;
//--- Prepare
if(!CAp::Assert(npoints>0))
return;
//--- check
if(!CAp::Assert(idx2>=idx1))
return;
useevs=flags/m_dfuseevs%2!=0;
//--- Leaf node
if(idx2==idx1)
{
bufs.m_treebuf[numprocessed]=-1;
bufs.m_treebuf[numprocessed+1]=xy[bufs.m_idxbuf[idx1]][nvars];
numprocessed=numprocessed+m_leafnodewidth;
//--- exit the function
return;
}
//--- Non-leaf node.
//--- Select random variable,prepare split:
//--- 1. prepare default solution - no splitting,class at random
//--- 2. investigate possible splits,compare with default/best
idxbest=-1;
//--- check
if(nclasses>1)
{
//--- default solution for classification
for(i=0;i<=nclasses-1;i++)
bufs.m_classibuf[i]=0;
s=idx2-idx1+1;
for(i=idx1;i<=idx2;i++)
{
j=(int)MathRound(xy[bufs.m_idxbuf[i]][nvars]);
bufs.m_classibuf[j]=bufs.m_classibuf[j]+1;
}
//--- calculation
ebest=0;
for(i=0;i<=nclasses-1;i++)
ebest=ebest+bufs.m_classibuf[i]*CMath::Sqr(1-bufs.m_classibuf[i]/s)+(s-bufs.m_classibuf[i])*CMath::Sqr(bufs.m_classibuf[i]/s);
ebest=MathSqrt(ebest/(nclasses*(idx2-idx1+1)));
}
else
{
//--- default solution for regression
v=0;
for(i=idx1;i<=idx2;i++)
v=v+xy[bufs.m_idxbuf[i]][nvars];
v=v/(idx2-idx1+1);
//--- calculation
ebest=0;
for(i=idx1;i<=idx2;i++)
ebest=ebest+CMath::Sqr(xy[bufs.m_idxbuf[i]][nvars]-v);
ebest=MathSqrt(ebest/(idx2-idx1+1));
}
//--- change value
i=0;
//--- cycle
while(i<=MathMin(nfeatures,nvarsinpool)-1)
{
//--- select variables from pool
j=i+CMath::RandomInteger(nvarsinpool-i);
k=bufs.m_varpool[i];
bufs.m_varpool[i]=bufs.m_varpool[j];
bufs.m_varpool[j]=k;
varcur=bufs.m_varpool[i];
//--- load variable values to working array
//--- apply EVS preprocessing: if all variable values are same,
//--- variable is excluded from pool.
//--- This is necessary for binary pre-splits (see later) to work.
for(j=idx1;j<=idx2;j++)
bufs.m_tmpbufr[j-idx1]=xy[bufs.m_idxbuf[j]][varcur];
//--- check
if(useevs)
{
bflag=false;
v=bufs.m_tmpbufr[0];
for(j=0;j<=idx2-idx1;j++)
{
//--- check
if(bufs.m_tmpbufr[j]!=v)
{
bflag=true;
break;
}
}
//--- check
if(!bflag)
{
//--- exclude variable from pool,
//--- go to the next iteration.
//--- I is not increased.
k=bufs.m_varpool[i];
bufs.m_varpool[i]=bufs.m_varpool[nvarsinpool-1];
bufs.m_varpool[nvarsinpool-1]=k;
nvarsinpool=nvarsinpool-1;
continue;
}
}
//--- load labels to working array
if(nclasses>1)
{
for(j=idx1;j<=idx2;j++)
bufs.m_tmpbufi[j-idx1]=(int)MathRound(xy[bufs.m_idxbuf[j]][nvars]);
}
else
{
for(j=idx1;j<=idx2;j++)
bufs.m_tmpbufr2[j-idx1]=xy[bufs.m_idxbuf[j]][nvars];
}
//--- calculate split
if(useevs && bufs.m_evsbin[varcur])
{
//--- Pre-calculated splits for binary variables.
//--- Threshold is already known,just calculate RMS error
threshold=bufs.m_evssplits[varcur];
//--- check
if(nclasses>1)
{
//--- classification-specific code
for(j=0;j<=2*nclasses-1;j++)
bufs.m_classibuf[j]=0;
//--- change values
sl=0;
sr=0;
//--- calculation
for(j=0;j<=idx2-idx1;j++)
{
k=bufs.m_tmpbufi[j];
//--- check
if(bufs.m_tmpbufr[j]<threshold)
{
bufs.m_classibuf[k]=bufs.m_classibuf[k]+1;
sl=sl+1;
}
else
{
bufs.m_classibuf[k+nclasses]=bufs.m_classibuf[k+nclasses]+1;
sr=sr+1;
}
}
//--- check
if(!CAp::Assert(sl!=0.0 && sr!=0.0,__FUNCTION__+": something strange!"))
return;
//--- change values
currms=0;
//--- calculation
for(j=0;j<=nclasses-1;j++)
{
w=bufs.m_classibuf[j];
currms=currms+w*CMath::Sqr(w/sl-1);
currms=currms+(sl-w)*CMath::Sqr(w/sl);
w=bufs.m_classibuf[nclasses+j];
currms=currms+w*CMath::Sqr(w/sr-1);
currms=currms+(sr-w)*CMath::Sqr(w/sr);
}
currms=MathSqrt(currms/(nclasses*(idx2-idx1+1)));
}
else
{
//--- regression-specific code
sl=0;
sr=0;
v1=0;
v2=0;
//--- calculation
for(j=0;j<=idx2-idx1;j++)
{
//--- check
if(bufs.m_tmpbufr[j]<threshold)
{
v1=v1+bufs.m_tmpbufr2[j];
sl=sl+1;
}
else
{
v2=v2+bufs.m_tmpbufr2[j];
sr=sr+1;
}
}
//--- check
if(!CAp::Assert(sl!=0.0 && sr!=0.0,__FUNCTION__+": something strange!"))
return;
//--- change values
v1=v1/sl;
v2=v2/sr;
currms=0;
for(j=0;j<=idx2-idx1;j++)
{
//--- check
if(bufs.m_tmpbufr[j]<threshold)
currms=currms+CMath::Sqr(v1-bufs.m_tmpbufr2[j]);
else
currms=currms+CMath::Sqr(v2-bufs.m_tmpbufr2[j]);
}
currms=MathSqrt(currms/(idx2-idx1+1));
}
//--- change value
info=1;
}
else
{
//--- Generic splits
if(nclasses>1)
DFSplitC(bufs.m_tmpbufr,bufs.m_tmpbufi,bufs.m_classibuf,idx2-idx1+1,nclasses,m_dfusestrongsplits,info,threshold,currms,bufs.m_sortrbuf,bufs.m_sortibuf);
else
DFSplitR(bufs.m_tmpbufr,bufs.m_tmpbufr2,idx2-idx1+1,m_dfusestrongsplits,info,threshold,currms,bufs.m_sortrbuf,bufs.m_sortrbuf2);
}
//--- check
if(info>0)
{
//--- check
if(currms<=ebest)
{
ebest=currms;
idxbest=varcur;
tbest=threshold;
}
}
//--- Next iteration
i=i+1;
}
//--- to split or not to split
if(idxbest<0)
{
//--- All values are same,cannot split.
bufs.m_treebuf[numprocessed]=-1;
//--- check
if(nclasses>1)
{
//--- Select random class label (randomness allows us to
//--- approximate distribution of the classes)
bufs.m_treebuf[numprocessed+1]=(int)MathRound(xy[bufs.m_idxbuf[idx1+CMath::RandomInteger(idx2-idx1+1)]][nvars]);
}
else
{
//--- Select average (for regression task).
v=0;
for(i=idx1;i<=idx2;i++)
v=v+xy[bufs.m_idxbuf[i]][nvars]/(idx2-idx1+1);
bufs.m_treebuf[numprocessed+1]=v;
}
//--- change value
numprocessed=numprocessed+m_leafnodewidth;
}
else
{
//--- we can split
bufs.m_treebuf[numprocessed]=idxbest;
bufs.m_treebuf[numprocessed+1]=tbest;
i1=idx1;
i2=idx2;
//--- cycle
while(i1<=i2)
{
//--- Reorder indices so that left partition is in [Idx1..I1-1],
//--- and right partition is in [I2+1..Idx2]
if(xy[bufs.m_idxbuf[i1]][idxbest]<tbest)
{
i1=i1+1;
continue;
}
//--- check
if(xy[bufs.m_idxbuf[i2]][idxbest]>=tbest)
{
i2=i2-1;
continue;
}
//--- change values
j=bufs.m_idxbuf[i1];
bufs.m_idxbuf[i1]=bufs.m_idxbuf[i2];
bufs.m_idxbuf[i2]=j;
i1=i1+1;
i2=i2-1;
}
//--- change values
oldnp=numprocessed;
numprocessed=numprocessed+m_innernodewidth;
//--- function call
DFBuildTreeRec(xy,npoints,nvars,nclasses,nfeatures,nvarsinpool,flags,numprocessed,idx1,i1-1,bufs);
bufs.m_treebuf[oldnp+2]=numprocessed;
//--- function call
DFBuildTreeRec(xy,npoints,nvars,nclasses,nfeatures,nvarsinpool,flags,numprocessed,i2+1,idx2,bufs);
}
}
//+------------------------------------------------------------------+
//| Makes split on attribute |
//+------------------------------------------------------------------+
static void CDForest::DFSplitC(double &x[],int &c[],int &cntbuf[],const int n,
const int nc,const int flags,int &info,
double &threshold,double &e,double &sortrbuf[],
int &sortibuf[])
{
//--- create variables
int i=0;
int neq=0;
int nless=0;
int ngreater=0;
int q=0;
int qmin=0;
int qmax=0;
int qcnt=0;
double cursplit=0;
int nleft=0;
double v=0;
double cure=0;
double w=0;
double sl=0;
double sr=0;
//--- initialization
info=0;
threshold=0;
e=0;
//--- function call
CTSort::TagSortFastI(x,c,sortrbuf,sortibuf,n);
//--- change values
e=CMath::m_maxrealnumber;
threshold=0.5*(x[0]+x[n-1]);
info=-3;
//--- check
if(flags/m_dfusestrongsplits%2==0)
{
//--- weak splits,split at half
qcnt=2;
qmin=1;
qmax=1;
}
else
{
//--- strong splits: choose best quartile
qcnt=4;
qmin=1;
qmax=3;
}
for(q=qmin;q<=qmax;q++)
{
//--- change values
cursplit=x[n*q/qcnt];
neq=0;
nless=0;
ngreater=0;
//--- calculation
for(i=0;i<=n-1;i++)
{
//--- check
if(x[i]<cursplit)
nless=nless+1;
//--- check
if(x[i]==cursplit)
neq=neq+1;
//--- check
if(x[i]>cursplit)
ngreater=ngreater+1;
}
//--- check
if(!CAp::Assert(neq!=0,__FUNCTION__+": NEq=0,something strange!!!"))
return;
//--- check
if(nless!=0 || ngreater!=0)
{
//--- set threshold between two partitions, with
//--- some tweaking to avoid problems with floating point
//--- arithmetics.
//--- The problem is that when you calculates C = 0.5*(A+B) there
//--- can be no C which lies strictly between A and B (for example,
//--- there is no floating point number which is
//--- greater than 1 and less than 1+eps). In such situations
//--- we choose right side as theshold (remember that
//--- points which lie on threshold falls to the right side).
if(nless<ngreater)
{
cursplit=0.5*(x[nless+neq-1]+x[nless+neq]);
nleft=nless+neq;
//--- check
if(cursplit<=(double)(x[nless+neq-1]))
cursplit=x[nless+neq];
}
else
{
cursplit=0.5*(x[nless-1]+x[nless]);
nleft=nless;
//--- check
if(cursplit<=(double)(x[nless-1]))
cursplit=x[nless];
}
//--- change value
info=1;
cure=0;
//--- calculation
for(i=0;i<=2*nc-1;i++)
cntbuf[i]=0;
for(i=0;i<=nleft-1;i++)
cntbuf[c[i]]=cntbuf[c[i]]+1;
for(i=nleft;i<=n-1;i++)
cntbuf[nc+c[i]]=cntbuf[nc+c[i]]+1;
//--- change values
sl=nleft;
sr=n-nleft;
v=0;
//--- calculation
for(i=0;i<=nc-1;i++)
{
w=cntbuf[i];
v=v+w*CMath::Sqr(w/sl-1);
v=v+(sl-w)*CMath::Sqr(w/sl);
w=cntbuf[nc+i];
v=v+w*CMath::Sqr(w/sr-1);
v=v+(sr-w)*CMath::Sqr(w/sr);
}
cure=MathSqrt(v/(nc*n));
//--- check
if(cure<e)
{
threshold=cursplit;
e=cure;
}
}
}
}
//+------------------------------------------------------------------+
//| Makes split on attribute |
//+------------------------------------------------------------------+
static void CDForest::DFSplitR(double &x[],double &y[],const int n,const int flags,
int &info,double &threshold,double &e,
double &sortrbuf[],double &sortrbuf2[])
{
//--- create variables
int i=0;
int neq=0;
int nless=0;
int ngreater=0;
int q=0;
int qmin=0;
int qmax=0;
int qcnt=0;
double cursplit=0;
int nleft=0;
double v=0;
double cure=0;
//--- initialization
info=0;
threshold=0;
e=0;
//--- function call
CTSort::TagSortFastR(x,y,sortrbuf,sortrbuf2,n);
//--- change values
e=CMath::m_maxrealnumber;
threshold=0.5*(x[0]+x[n-1]);
info=-3;
//--- check
if(flags/m_dfusestrongsplits%2==0)
{
//--- weak splits,split at half
qcnt=2;
qmin=1;
qmax=1;
}
else
{
//--- strong splits: choose best quartile
qcnt=4;
qmin=1;
qmax=3;
}
//--- calculation
for(q=qmin;q<=qmax;q++)
{
//--- change values
cursplit=x[n*q/qcnt];
neq=0;
nless=0;
ngreater=0;
for(i=0;i<=n-1;i++)
{
//--- check
if(x[i]<cursplit)
nless=nless+1;
//--- check
if(x[i]==cursplit)
neq=neq+1;
//--- check
if(x[i]>cursplit)
ngreater=ngreater+1;
}
//--- check
if(!CAp::Assert(neq!=0,__FUNCTION__+": NEq=0,something strange!!!"))
return;
//--- check
if(nless!=0 || ngreater!=0)
{
//--- set threshold between two partitions, with
//--- some tweaking to avoid problems with floating point
//--- arithmetics.
//--- The problem is that when you calculates C = 0.5*(A+B) there
//--- can be no C which lies strictly between A and B (for example,
//--- there is no floating point number which is
//--- greater than 1 and less than 1+eps). In such situations
//--- we choose right side as theshold (remember that
//--- points which lie on threshold falls to the right side).
if(nless<ngreater)
{
cursplit=0.5*(x[nless+neq-1]+x[nless+neq]);
nleft=nless+neq;
//--- check
if(cursplit<=(double)(x[nless+neq-1]))
cursplit=x[nless+neq];
}
else
{
cursplit=0.5*(x[nless-1]+x[nless]);
nleft=nless;
//--- check
if(cursplit<=(double)(x[nless-1]))
cursplit=x[nless];
}
//--- change value
info=1;
cure=0;
v=0;
//--- calculation
for(i=0;i<=nleft-1;i++)
v=v+y[i];
v=v/nleft;
for(i=0;i<=nleft-1;i++)
cure=cure+CMath::Sqr(y[i]-v);
v=0;
for(i=nleft;i<=n-1;i++)
v=v+y[i];
v=v/(n-nleft);
for(i=nleft;i<=n-1;i++)
cure=cure+CMath::Sqr(y[i]-v);
cure=MathSqrt(cure/n);
//--- check
if(cure<e)
{
threshold=cursplit;
e=cure;
}
}
}
}
//+------------------------------------------------------------------+
//| Middle and clusterization |
//+------------------------------------------------------------------+
class CKMeans
{
private:
//--- private method
static bool SelectCenterPP(CMatrixDouble &xy,const int npoints,const int nvars,CMatrixDouble &centers,bool &cbusycenters[],const int ccnt,double &d2[],double &p[],double &tmp[]);
public:
//--- constructor, destructor
CKMeans(void);
~CKMeans(void);
//--- public method
static void KMeansGenerate(CMatrixDouble &xy,const int npoints,const int nvars,const int k,const int restarts,int &info,CMatrixDouble &c,int &xyc[]);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CKMeans::CKMeans(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CKMeans::~CKMeans(void)
{
}
//+------------------------------------------------------------------+
//| k-means++ clusterization |
//| INPUT PARAMETERS: |
//| XY - dataset, array [0..NPoints-1,0..NVars-1]. |
//| NPoints - dataset size, NPoints>=K |
//| NVars - number of variables, NVars>=1 |
//| K - desired number of clusters, K>=1 |
//| Restarts - number of restarts, Restarts>=1 |
//| OUTPUT PARAMETERS: |
//| Info - return code: |
//| * -3, if task is degenerate (number of |
//| distinct points is less than K) |
//| * -1, if incorrect |
//| NPoints/NFeatures/K/Restarts was passed|
//| * 1, if subroutine finished successfully |
//| C - array[0..NVars-1,0..K-1].matrix whose columns|
//| store cluster's centers |
//| XYC - array[NPoints], which contains cluster |
//| indexes |
//+------------------------------------------------------------------+
static void CKMeans::KMeansGenerate(CMatrixDouble &xy,const int npoints,
const int nvars,const int k,
const int restarts,int &info,
CMatrixDouble &c,int &xyc[])
{
//--- create variables
int i=0;
int j=0;
double e=0;
double ebest=0;
double v=0;
int cclosest=0;
bool waschanges;
bool zerosizeclusters;
int pass=0;
int i_=0;
double dclosest=0;
//--- creating arrays
int xycbest[];
double x[];
double tmp[];
double d2[];
double p[];
int csizes[];
bool cbusy[];
double work[];
//--- create matrix
CMatrixDouble ct;
CMatrixDouble ctbest;
//--- initialization
info=0;
//--- Test parameters
if(npoints<k || nvars<1 || k<1 || restarts<1)
{
info=-1;
return;
}
//--- TODO: special case K=1
//--- TODO: special case K=NPoints
info=1;
//--- Multiple passes of k-means++ algorithm
ct.Resize(k,nvars);
ctbest.Resize(k,nvars);
ArrayResizeAL(xyc,npoints);
ArrayResizeAL(xycbest,npoints);
ArrayResizeAL(d2,npoints);
ArrayResizeAL(p,npoints);
ArrayResizeAL(tmp,nvars);
ArrayResizeAL(csizes,k);
ArrayResizeAL(cbusy,k);
//--- change value
ebest=CMath::m_maxrealnumber;
//--- calculation
for(pass=1;pass<=restarts;pass++)
{
//--- Select initial centers using k-means++ algorithm
//--- 1. Choose first center at random
//--- 2. Choose next centers using their distance from centers already chosen
//--- Note that for performance reasons centers are stored in ROWS of CT,not
//--- in columns. We'll transpose CT in the end and store it in the C.
i=CMath::RandomInteger(npoints);
for(i_=0;i_<=nvars-1;i_++)
ct[0].Set(i_,xy[i][i_]);
cbusy[0]=true;
for(i=1;i<=k-1;i++)
cbusy[i]=false;
//--- check
if(!SelectCenterPP(xy,npoints,nvars,ct,cbusy,k,d2,p,tmp))
{
info=-3;
return;
}
//--- Update centers:
//--- 2. update center positions
for(i=0;i<=npoints-1;i++)
xyc[i]=-1;
//--- cycle
while(true)
{
//--- fill XYC with center numbers
waschanges=false;
for(i=0;i<=npoints-1;i++)
{
//--- change values
cclosest=-1;
dclosest=CMath::m_maxrealnumber;
for(j=0;j<=k-1;j++)
{
//--- calculation
for(i_=0;i_<=nvars-1;i_++)
tmp[i_]=xy[i][i_];
for(i_=0;i_<=nvars-1;i_++)
tmp[i_]=tmp[i_]-ct[j][i_];
v=0.0;
for(i_=0;i_<=nvars-1;i_++)
v+=tmp[i_]*tmp[i_];
//--- check
if(v<dclosest)
{
cclosest=j;
dclosest=v;
}
}
//--- check
if(xyc[i]!=cclosest)
waschanges=true;
//--- change value
xyc[i]=cclosest;
}
//--- Update centers
for(j=0;j<=k-1;j++)
csizes[j]=0;
for(i=0;i<=k-1;i++)
{
for(j=0;j<=nvars-1;j++)
ct[i].Set(j,0);
}
//--- change values
for(i=0;i<=npoints-1;i++)
{
csizes[xyc[i]]=csizes[xyc[i]]+1;
for(i_=0;i_<=nvars-1;i_++)
ct[xyc[i]].Set(i_,ct[xyc[i]][i_]+xy[i][i_]);
}
zerosizeclusters=false;
for(i=0;i<=k-1;i++)
{
cbusy[i]=csizes[i]!=0;
zerosizeclusters=zerosizeclusters || csizes[i]==0;
}
//--- check
if(zerosizeclusters)
{
//--- Some clusters have zero size - rare,but possible.
//--- We'll choose new centers for such clusters using k-means++ rule
//--- and restart algorithm
if(!SelectCenterPP(xy,npoints,nvars,ct,cbusy,k,d2,p,tmp))
{
info=-3;
return;
}
continue;
}
//--- copy
for(j=0;j<=k-1;j++)
{
v=1.0/(double)csizes[j];
for(i_=0;i_<=nvars-1;i_++)
ct[j].Set(i_,v*ct[j][i_]);
}
//--- if nothing has changed during iteration
if(!waschanges)
break;
}
//--- 3. Calculate E,compare with best centers found so far
e=0;
for(i=0;i<=npoints-1;i++)
{
for(i_=0;i_<=nvars-1;i_++)
tmp[i_]=xy[i][i_];
for(i_=0;i_<=nvars-1;i_++)
tmp[i_]=tmp[i_]-ct[xyc[i]][i_];
//--- calculation
v=0.0;
for(i_=0;i_<=nvars-1;i_++)
v+=tmp[i_]*tmp[i_];
e=e+v;
}
//--- check
if(e<ebest)
{
//--- store partition.
ebest=e;
//--- function call
CBlas::CopyMatrix(ct,0,k-1,0,nvars-1,ctbest,0,k-1,0,nvars-1);
//--- copy
for(i=0;i<=npoints-1;i++)
xycbest[i]=xyc[i];
}
}
//--- Copy and transpose
c.Resize(nvars,k);
//--- function call
CBlas::CopyAndTranspose(ctbest,0,k-1,0,nvars-1,c,0,nvars-1,0,k-1);
//--- copy
for(i=0;i<=npoints-1;i++)
xyc[i]=xycbest[i];
}
//+------------------------------------------------------------------+
//| Select center for a new cluster using k-means++ rule |
//+------------------------------------------------------------------+
static bool CKMeans::SelectCenterPP(CMatrixDouble &xy,const int npoints,
const int nvars,CMatrixDouble &centers,
bool &cbusycenters[],const int ccnt,
double &d2[],double &p[],double &tmp[])
{
//--- create variables
bool result;
int i=0;
int j=0;
int cc=0;
double v=0;
double s=0;
int i_=0;
//--- create array
double busycenters[];
//--- copy
ArrayCopy(busycenters,cbusycenters);
//--- initialization
result=true;
//--- calculation
for(cc=0;cc<=ccnt-1;cc++)
{
//--- check
if(!busycenters[cc])
{
//--- fill D2
for(i=0;i<=npoints-1;i++)
{
d2[i]=CMath::m_maxrealnumber;
for(j=0;j<=ccnt-1;j++)
{
//--- check
if(busycenters[j])
{
for(i_=0;i_<=nvars-1;i_++)
tmp[i_]=xy[i][i_];
for(i_=0;i_<=nvars-1;i_++)
tmp[i_]=tmp[i_]-centers[j][i_];
//--- calculation
v=0.0;
for(i_=0;i_<=nvars-1;i_++)
v+=tmp[i_]*tmp[i_];
//--- check
if(v<d2[i])
d2[i]=v;
}
}
}
//--- calculate P (non-cumulative)
s=0;
for(i=0;i<=npoints-1;i++)
s=s+d2[i];
//--- check
if(s==0.0)
return(false);
//--- change value
s=1/s;
for(i_=0;i_<=npoints-1;i_++)
p[i_]=s*d2[i_];
//--- choose one of points with probability P
//--- random number within (0,1) is generated and
//--- inverse empirical CDF is used to randomly choose a point.
s=0;
v=CMath::RandomReal();
//--- calculation
for(i=0;i<=npoints-1;i++)
{
s=s+p[i];
//--- check
if(v<=s || i==npoints-1)
{
for(i_=0;i_<=nvars-1;i_++)
centers[cc].Set(i_,xy[i][i_]);
busycenters[cc]=true;
//--- break the cycle
break;
}
}
}
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Multiclass Fisher LDA |
//+------------------------------------------------------------------+
class CLDA
{
public:
//--- constructor, destructor
CLDA(void);
~CLDA(void);
//--- methods
static void FisherLDA(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,int &info,double &w[]);
static void FisherLDAN(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,int &info,CMatrixDouble &w);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CLDA::CLDA(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CLDA::~CLDA(void)
{
}
//+------------------------------------------------------------------+
//| Multiclass Fisher LDA |
//| Subroutine finds coefficients of linear combination which |
//| optimally separates training set on classes. |
//| INPUT PARAMETERS: |
//| XY - training set, array[0..NPoints-1,0..NVars]. |
//| First NVars columns store values of |
//| independent variables, next column stores |
//| number of class (from 0 to NClasses-1) which |
//| dataset element belongs to. Fractional values|
//| are rounded to nearest integer. |
//| NPoints - training set size, NPoints>=0 |
//| NVars - number of independent variables, NVars>=1 |
//| NClasses - number of classes, NClasses>=2 |
//| OUTPUT PARAMETERS: |
//| Info - return code: |
//| * -4, if internal EVD subroutine hasn't |
//| converged |
//| * -2, if there is a point with class number |
//| outside of [0..NClasses-1]. |
//| * -1, if incorrect parameters was passed |
//| (NPoints<0, NVars<1, NClasses<2) |
//| * 1, if task has been solved |
//| * 2, if there was a multicollinearity in |
//| training set, but task has been solved.|
//| W - linear combination coefficients, |
//| array[0..NVars-1] |
//+------------------------------------------------------------------+
static void CLDA::FisherLDA(CMatrixDouble &xy,const int npoints,
const int nvars,const int nclasses,
int &info,double &w[])
{
//--- create a variable
int i_=0;
//--- create matrix
CMatrixDouble w2;
//--- initialization
info=0;
//--- function call
FisherLDAN(xy,npoints,nvars,nclasses,info,w2);
//--- check
if(info>0)
{
//--- allocation
ArrayResizeAL(w,nvars);
//--- copy
for(i_=0;i_<=nvars-1;i_++)
w[i_]=w2[i_][0];
}
}
//+------------------------------------------------------------------+
//| N-dimensional multiclass Fisher LDA |
//| Subroutine finds coefficients of linear combinations which |
//| optimally separates |
//| training set on classes. It returns N-dimensional basis whose |
//| vector are sorted |
//| by quality of training set separation (in descending order). |
//| INPUT PARAMETERS: |
//| XY - training set, array[0..NPoints-1,0..NVars]. |
//| First NVars columns store values of |
//| independent variables, next column stores |
//| number of class (from 0 to NClasses-1) which |
//| dataset element belongs to. Fractional values|
//| are rounded to nearest integer. |
//| NPoints - training set size, NPoints>=0 |
//| NVars - number of independent variables, NVars>=1 |
//| NClasses - number of classes, NClasses>=2 |
//| OUTPUT PARAMETERS: |
//| Info - return code: |
//| * -4, if internal EVD subroutine hasn't |
//| converged |
//| * -2, if there is a point with class number |
//| outside of [0..NClasses-1]. |
//| * -1, if incorrect parameters was passed |
//| (NPoints<0, NVars<1, NClasses<2) |
//| * 1, if task has been solved |
//| * 2, if there was a multicollinearity in |
//| training set, but task has been solved.|
//| W - basis, array[0..NVars-1,0..NVars-1] |
//| columns of matrix stores basis vectors, |
//| sorted by quality of training set separation |
//| (in descending order) |
//+------------------------------------------------------------------+
static void CLDA::FisherLDAN(CMatrixDouble &xy,const int npoints,const int nvars,
const int nclasses,int &info,CMatrixDouble &w)
{
//--- create variables
int i=0;
int j=0;
int k=0;
int m=0;
double v=0;
int i_=0;
//--- creating arrays
int c[];
double mu[];
int nc[];
double tf[];
double d[];
double d2[];
double work[];
//--- create matrix
CMatrixDouble muc;
CMatrixDouble sw;
CMatrixDouble st;
CMatrixDouble z;
CMatrixDouble z2;
CMatrixDouble tm;
CMatrixDouble sbroot;
CMatrixDouble a;
CMatrixDouble xyproj;
CMatrixDouble wproj;
//--- initialization
info=0;
//--- Test data
if((npoints<0 || nvars<1) || nclasses<2)
{
info=-1;
return;
}
for(i=0;i<=npoints-1;i++)
{
//--- check
if((int)MathRound(xy[i][nvars])<0 || (int)MathRound(xy[i][nvars])>=nclasses)
{
info=-2;
return;
}
}
//--- change value
info=1;
//--- Special case: NPoints<=1
//--- Degenerate task.
if(npoints<=1)
{
info=2;
//--- allocation
w.Resize(nvars,nvars);
//--- initialization
for(i=0;i<=nvars-1;i++)
{
for(j=0;j<=nvars-1;j++)
{
//--- check
if(i==j)
w[i].Set(j,1);
else
w[i].Set(j,0);
}
}
//--- exit the function
return;
}
//--- Prepare temporaries
ArrayResizeAL(tf,nvars);
ArrayResizeAL(work,MathMax(nvars,npoints)+1);
//--- Convert class labels from reals to integers (just for convenience)
ArrayResizeAL(c,npoints);
for(i=0;i<=npoints-1;i++)
c[i]=(int)MathRound(xy[i][nvars]);
//--- Calculate class sizes and means
ArrayResizeAL(mu,nvars);
muc.Resize(nclasses,nvars);
ArrayResizeAL(nc,nclasses);
for(j=0;j<=nvars-1;j++)
mu[j]=0;
for(i=0;i<=nclasses-1;i++)
{
nc[i]=0;
for(j=0;j<=nvars-1;j++)
muc[i].Set(j,0);
}
//--- calculation
for(i=0;i<=npoints-1;i++)
{
for(i_=0;i_<=nvars-1;i_++)
mu[i_]=mu[i_]+xy[i][i_];
for(i_=0;i_<=nvars-1;i_++)
muc[c[i]].Set(i_,muc[c[i]][i_]+xy[i][i_]);
nc[c[i]]=nc[c[i]]+1;
}
for(i=0;i<=nclasses-1;i++)
{
v=1.0/(double)nc[i];
for(i_=0;i_<=nvars-1;i_++)
muc[i].Set(i_,v*muc[i][i_]);
}
//--- change values
v=1.0/(double)npoints;
for(i_=0;i_<=nvars-1;i_++)
mu[i_]=v*mu[i_];
//--- Create ST matrix
st.Resize(nvars,nvars);
for(i=0;i<=nvars-1;i++)
{
for(j=0;j<=nvars-1;j++)
st[i].Set(j,0);
}
//--- calculation
for(k=0;k<=npoints-1;k++)
{
for(i_=0;i_<=nvars-1;i_++)
tf[i_]=xy[k][i_];
for(i_=0;i_<=nvars-1;i_++)
tf[i_]=tf[i_]-mu[i_];
for(i=0;i<=nvars-1;i++)
{
v=tf[i];
for(i_=0;i_<=nvars-1;i_++)
st[i].Set(i_,st[i][i_]+v*tf[i_]);
}
}
//--- Create SW matrix
sw.Resize(nvars,nvars);
for(i=0;i<=nvars-1;i++)
{
for(j=0;j<=nvars-1;j++)
sw[i].Set(j,0);
}
//--- calculation
for(k=0;k<=npoints-1;k++)
{
for(i_=0;i_<=nvars-1;i_++)
tf[i_]=xy[k][i_];
for(i_=0;i_<=nvars-1;i_++)
tf[i_]=tf[i_]-muc[c[k]][i_];
for(i=0;i<=nvars-1;i++)
{
v=tf[i];
for(i_=0;i_<=nvars-1;i_++)
sw[i].Set(i_,sw[i][i_]+v*tf[i_]);
}
}
//--- Maximize ratio J=(w'*ST*w)/(w'*SW*w).
//--- First,make transition from w to v such that w'*ST*w becomes v'*v:
//--- v=root(ST)*w=R*w
//--- R=root(D)*Z'
//--- w=(root(ST)^-1)*v=RI*v
//--- RI=Z*inv(root(D))
//--- J=(v'*v)/(v'*(RI'*SW*RI)*v)
//--- ST=Z*D*Z'
//--- so we have
//--- J=(v'*v) / (v'*(inv(root(D))*Z'*SW*Z*inv(root(D)))*v)=
//=(v'*v) / (v'*A*v)
if(!CEigenVDetect::SMatrixEVD(st,nvars,1,true,d,z))
{
info=-4;
return;
}
//--- allocation
w.Resize(nvars,nvars);
//--- check
if(d[nvars-1]<=0.0 || d[0]<=1000*CMath::m_machineepsilon*d[nvars-1])
{
//--- Special case: D[NVars-1]<=0
//--- Degenerate task (all variables takes the same value).
if(d[nvars-1]<=0.0)
{
info=2;
for(i=0;i<=nvars-1;i++)
{
for(j=0;j<=nvars-1;j++)
{
//--- check
if(i==j)
w[i].Set(j,1);
else
w[i].Set(j,0);
}
}
//--- exit the function
return;
}
//--- Special case: degenerate ST matrix,multicollinearity found.
//--- Since we know ST eigenvalues/vectors we can translate task to
//--- non-degenerate form.
//--- Let WG is orthogonal basis of the non zero variance subspace
//--- of the ST and let WZ is orthogonal basis of the zero variance
//--- subspace.
//--- Projection on WG allows us to use LDA on reduced M-dimensional
//--- subspace,N-M vectors of WZ allows us to update reduced LDA
//--- factors to full N-dimensional subspace.
m=0;
for(k=0;k<=nvars-1;k++)
{
//--- check
if(d[k]<=1000*CMath::m_machineepsilon*d[nvars-1])
m=k+1;
}
//--- check
if(!CAp::Assert(m!=0,__FUNCTION__+": internal error #1"))
return;
//--- allocation
xyproj.Resize(npoints,nvars-m+1);
//--- function call
CBlas::MatrixMatrixMultiply(xy,0,npoints-1,0,nvars-1,false,z,0,nvars-1,m,nvars-1,false,1.0,xyproj,0,npoints-1,0,nvars-m-1,0.0,work);
for(i=0;i<=npoints-1;i++)
xyproj[i].Set(nvars-m,xy[i][nvars]);
//--- function call
FisherLDAN(xyproj,npoints,nvars-m,nclasses,info,wproj);
//--- check
if(info<0)
return;
//--- function call
CBlas::MatrixMatrixMultiply(z,0,nvars-1,m,nvars-1,false,wproj,0,nvars-m-1,0,nvars-m-1,false,1.0,w,0,nvars-1,0,nvars-m-1,0.0,work);
//--- change values
for(k=nvars-m;k<=nvars-1;k++)
{
for(i_=0;i_<=nvars-1;i_++)
w[i_].Set(k,z[i_][k-nvars+m]);
}
info=2;
}
else
{
//--- General case: no multicollinearity
tm.Resize(nvars,nvars);
a.Resize(nvars,nvars);
//--- function call
CBlas::MatrixMatrixMultiply(sw,0,nvars-1,0,nvars-1,false,z,0,nvars-1,0,nvars-1,false,1.0,tm,0,nvars-1,0,nvars-1,0.0,work);
CBlas::MatrixMatrixMultiply(z,0,nvars-1,0,nvars-1,true,tm,0,nvars-1,0,nvars-1,false,1.0,a,0,nvars-1,0,nvars-1,0.0,work);
//--- change values
for(i=0;i<=nvars-1;i++)
{
for(j=0;j<=nvars-1;j++)
a[i].Set(j,a[i][j]/MathSqrt(d[i]*d[j]));
}
//--- check
if(!CEigenVDetect::SMatrixEVD(a,nvars,1,true,d2,z2))
{
info=-4;
return;
}
//--- calculation
for(k=0;k<=nvars-1;k++)
{
for(i=0;i<=nvars-1;i++)
tf[i]=z2[i][k]/MathSqrt(d[i]);
for(i=0;i<=nvars-1;i++)
{
v=0.0;
for(i_=0;i_<=nvars-1;i_++)
v+=z[i][i_]*tf[i_];
w[i].Set(k,v);
}
}
}
//--- Post-processing:
//--- * normalization
//--- * converting to non-negative form,if possible
for(k=0;k<=nvars-1;k++)
{
//--- calculation
v=0.0;
for(i_=0;i_<=nvars-1;i_++)
v+=w[i_][k]*w[i_][k];
v=1/MathSqrt(v);
for(i_=0;i_<=nvars-1;i_++)
w[i_].Set(k,v*w[i_][k]);
v=0;
for(i=0;i<=nvars-1;i++)
v=v+w[i][k];
//--- check
if(v<0.0)
{
for(i_=0;i_<=nvars-1;i_++)
w[i_].Set(k,-1*w[i_][k]);
}
}
}
//+------------------------------------------------------------------+
//| Auxiliary class for CLinReg |
//+------------------------------------------------------------------+
class CLinearModel
{
public:
double m_w[];
//--- constructor, destructor
CLinearModel(void);
~CLinearModel(void);
//--- copy
void Copy(CLinearModel &obj);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CLinearModel::CLinearModel(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CLinearModel::~CLinearModel(void)
{
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CLinearModel::Copy(CLinearModel &obj)
{
//--- copy array
ArrayCopy(m_w,obj.m_w);
}
//+------------------------------------------------------------------+
//| This class is a shell for class CLinearModel |
//+------------------------------------------------------------------+
class CLinearModelShell
{
private:
CLinearModel m_innerobj;
public:
//--- constructors, destructor
CLinearModelShell(void);
CLinearModelShell(CLinearModel &obj);
~CLinearModelShell(void);
//--- method
CLinearModel *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CLinearModelShell::CLinearModelShell(void)
{
}
//+------------------------------------------------------------------+
//| Copy constructor |
//+------------------------------------------------------------------+
CLinearModelShell::CLinearModelShell(CLinearModel &obj)
{
//--- copy
m_innerobj.Copy(obj);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CLinearModelShell::~CLinearModelShell(void)
{
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CLinearModel *CLinearModelShell::GetInnerObj(void)
{
//--- return result
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| LRReport structure contains additional information about linear |
//| model: |
//| * C - covariation matrix, array[0..NVars,0..NVars].|
//| C[i,j] = Cov(A[i],A[j]) |
//| * RMSError - root mean square error on a training set |
//| * AvgError - average error on a training set |
//| * AvgRelError - average relative error on a training set |
//| (excluding observations with zero function |
//| value). |
//| * CVRMSError - leave-one-out cross-validation estimate of |
//| generalization error. Calculated using fast |
//| algorithm with O(NVars*NPoints) complexity. |
//| * CVAvgError - cross-validation estimate of average error |
//| * CVAvgRelError - cross-validation estimate of average relative|
//| error |
//| All other fields of the structure are intended for internal use |
//| and should not be used outside ALGLIB. |
//+------------------------------------------------------------------+
class CLRReport
{
public:
//--- variables
double m_rmserror;
double m_avgerror;
double m_avgrelerror;
double m_cvrmserror;
double m_cvavgerror;
double m_cvavgrelerror;
int m_ncvdefects;
//--- array
int m_cvdefects[];
//--- matrix
CMatrixDouble m_c;
//--- constructor, destructor
CLRReport(void);
~CLRReport(void);
//--- copy
void Copy(CLRReport &obj);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CLRReport::CLRReport(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CLRReport::~CLRReport(void)
{
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CLRReport::Copy(CLRReport &obj)
{
//--- copy variables
m_rmserror=obj.m_rmserror;
m_avgerror=obj.m_avgerror;
m_avgrelerror=obj.m_avgrelerror;
m_cvrmserror=obj.m_cvrmserror;
m_cvavgerror=obj.m_cvavgerror;
m_cvavgrelerror=obj.m_cvavgrelerror;
m_ncvdefects=obj.m_ncvdefects;
//--- copy array
ArrayCopy(m_cvdefects,obj.m_cvdefects);
//--- copy matrix
m_c=obj.m_c;
}
//+------------------------------------------------------------------+
//| LRReport structure contains additional information about linear |
//| model: |
//| * C - covariation matrix, array[0..NVars,0..NVars].|
//| C[i,j]=Cov(A[i],A[j]) |
//| * RMSError - root mean square error on a training set |
//| * AvgError - average error on a training set |
//| * AvgRelError - average relative error on a training set |
//| (excluding observations with zero function |
//| value). |
//| * CVRMSError - leave-one-out cross-validation estimate of |
//| generalization error. Calculated using fast |
//| algorithm with O(NVars*NPoints) complexity. |
//| * CVAvgError - cross-validation estimate of average error |
//| * CVAvgRelError - cross-validation estimate of average relative|
//| error |
//| All other fields of the structure are intended for internal use |
//| and should not be used outside ALGLIB. |
//+------------------------------------------------------------------+
class CLRReportShell
{
private:
CLRReport m_innerobj;
public:
//--- constructors, destructor
CLRReportShell(void);
CLRReportShell(CLRReport &obj);
~CLRReportShell(void);
//--- methods
double GetRMSError(void);
void SetRMSError(const double d);
double GetAvgError(void);
void SetAvgError(const double d);
double GetAvgRelError(void);
void SetAvgRelError(const double d);
double GetCVRMSError(void);
void SetCVRMSError(const double d);
double GetCVAvgError(void);
void SetCVAvgError(const double d);
double GetCVAvgRelError(void);
void SetCVAvgRelError(const double d);
int GetNCVDEfects(void);
void SetNCVDEfects(const int i);
CLRReport *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CLRReportShell::CLRReportShell(void)
{
}
//+------------------------------------------------------------------+
//| Copy constructor |
//+------------------------------------------------------------------+
CLRReportShell::CLRReportShell(CLRReport &obj)
{
//--- copy
m_innerobj.Copy(obj);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CLRReportShell::~CLRReportShell(void)
{
}
//+------------------------------------------------------------------+
//| Returns the value of the variable rmserror |
//+------------------------------------------------------------------+
double CLRReportShell::GetRMSError(void)
{
//--- return result
return(m_innerobj.m_rmserror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable rmserror |
//+------------------------------------------------------------------+
void CLRReportShell::SetRMSError(const double d)
{
//--- change value
m_innerobj.m_rmserror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgerror |
//+------------------------------------------------------------------+
double CLRReportShell::GetAvgError(void)
{
//--- return result
return(m_innerobj.m_avgerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgerror |
//+------------------------------------------------------------------+
void CLRReportShell::SetAvgError(const double d)
{
//--- change value
m_innerobj.m_avgerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgrelerror |
//+------------------------------------------------------------------+
double CLRReportShell::GetAvgRelError(void)
{
//--- return result
return(m_innerobj.m_avgrelerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgrelerror |
//+------------------------------------------------------------------+
void CLRReportShell::SetAvgRelError(const double d)
{
//--- change value
m_innerobj.m_avgrelerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable cvrmserror |
//+------------------------------------------------------------------+
double CLRReportShell::GetCVRMSError(void)
{
//--- return result
return(m_innerobj.m_cvrmserror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable cvrmserror |
//+------------------------------------------------------------------+
void CLRReportShell::SetCVRMSError(const double d)
{
//--- change value
m_innerobj.m_cvrmserror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable cvavgerror |
//+------------------------------------------------------------------+
double CLRReportShell::GetCVAvgError(void)
{
//--- return result
return(m_innerobj.m_cvavgerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable cvavgerror |
//+------------------------------------------------------------------+
void CLRReportShell::SetCVAvgError(const double d)
{
//--- change value
m_innerobj.m_cvavgerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable cvavgrelerror |
//+------------------------------------------------------------------+
double CLRReportShell::GetCVAvgRelError(void)
{
//--- return result
return(m_innerobj.m_cvavgrelerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable cvavgrelerror |
//+------------------------------------------------------------------+
void CLRReportShell::SetCVAvgRelError(const double d)
{
//--- change value
m_innerobj.m_cvavgrelerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable ncvdefects |
//+------------------------------------------------------------------+
int CLRReportShell::GetNCVDEfects(void)
{
//--- return result
return(m_innerobj.m_ncvdefects);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable ncvdefects |
//+------------------------------------------------------------------+
void CLRReportShell::SetNCVDEfects(const int i)
{
//--- change value
m_innerobj.m_ncvdefects=i;
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CLRReport *CLRReportShell::GetInnerObj(void)
{
//--- return result
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Linear regression class |
//+------------------------------------------------------------------+
class CLinReg
{
private:
//--- private method
static void LRInternal(CMatrixDouble &xy,double &s[],const int npoints,const int nvars,int &info,CLinearModel &lm,CLRReport &ar);
public:
//--- constant
static const int m_lrvnum;
//--- constructor, destructor
CLinReg(void);
~CLinReg(void);
//--- public methods
static void LRBuild(CMatrixDouble &xy,const int npoints,const int nvars,int &info,CLinearModel &lm,CLRReport &ar);
static void LRBuildS(CMatrixDouble &xy,double &s[],const int npoints,const int nvars,int &info,CLinearModel &lm,CLRReport &ar);
static void LRBuildZS(CMatrixDouble &xy,double &s[],const int npoints,const int nvars,int &info,CLinearModel &lm,CLRReport &ar);
static void LRBuildZ(CMatrixDouble &xy,const int npoints,const int nvars,int &info,CLinearModel &lm,CLRReport &ar);
static void LRUnpack(CLinearModel &lm,double &v[],int &nvars);
static void LRPack(double &v[],const int nvars,CLinearModel &lm);
static double LRProcess(CLinearModel &lm,double &x[]);
static double LRRMSError(CLinearModel &lm,CMatrixDouble &xy,const int npoints);
static double LRAvgError(CLinearModel &lm,CMatrixDouble &xy,const int npoints);
static double LRAvgRelError(CLinearModel &lm,CMatrixDouble &xy,const int npoints);
static void LRCopy(CLinearModel &lm1,CLinearModel &lm2);
static void LRLines(CMatrixDouble &xy,double &s[],const int n,int &info,double &a,double &b,double &vara,double &varb,double &covab,double &corrab,double &p);
static void LRLine(CMatrixDouble &xy,const int n,int &info,double &a,double &b);
};
//+------------------------------------------------------------------+
//| Initialize constant |
//+------------------------------------------------------------------+
const int CLinReg::m_lrvnum=5;
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CLinReg::CLinReg(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CLinReg::~CLinReg(void)
{
}
//+------------------------------------------------------------------+
//| Linear regression |
//| Subroutine builds model: |
//| Y = A(0)*X[0] + ... + A(N-1)*X[N-1] + A(N) |
//| and model found in ALGLIB format, covariation matrix, training |
//| set errors (rms, average, average relative) and leave-one-out |
//| cross-validation estimate of the generalization error. CV |
//| estimate calculated using fast algorithm with O(NPoints*NVars) |
//| complexity. |
//| When covariation matrix is calculated standard deviations of|
//| function values are assumed to be equal to RMS error on the |
//| training set. |
//| INPUT PARAMETERS: |
//| XY - training set, array [0..NPoints-1,0..NVars]: |
//| * NVars columns - independent variables |
//| * last column - dependent variable |
//| NPoints - training set size, NPoints>NVars+1 |
//| NVars - number of independent variables |
//| OUTPUT PARAMETERS: |
//| Info - return code: |
//| * -255, in case of unknown internal error |
//| * -4, if internal SVD subroutine haven't |
//| converged |
//| * -1, if incorrect parameters was passed |
//| (NPoints<NVars+2, NVars<1). |
//| * 1, if subroutine successfully finished |
//| LM - linear model in the ALGLIB format. Use |
//| subroutines of this unit to work with the |
//| model. |
//| AR - additional results |
//+------------------------------------------------------------------+
static void CLinReg::LRBuild(CMatrixDouble &xy,const int npoints,const int nvars,
int &info,CLinearModel &lm,CLRReport &ar)
{
//--- create variables
int i=0;
double sigma2=0;
int i_=0;
//--- create array
double s[];
//--- initialization
info=0;
//--- check
if(npoints<=nvars+1 || nvars<1)
{
info=-1;
return;
}
//--- allocation
ArrayResizeAL(s,npoints);
for(i=0;i<=npoints-1;i++)
s[i]=1;
//--- function call
LRBuildS(xy,s,npoints,nvars,info,lm,ar);
//--- check
if(info<0)
return;
//--- calculation
sigma2=CMath::Sqr(ar.m_rmserror)*npoints/(npoints-nvars-1);
for(i=0;i<=nvars;i++)
{
for(i_=0;i_<=nvars;i_++)
ar.m_c[i].Set(i_,sigma2*ar.m_c[i][i_]);
}
}
//+------------------------------------------------------------------+
//| Linear regression |
//| Variant of LRBuild which uses vector of standatd deviations |
//| (errors in function values). |
//| INPUT PARAMETERS: |
//| XY - training set, array [0..NPoints-1,0..NVars]: |
//| * NVars columns - independent variables |
//| * last column - dependent variable |
//| S - standard deviations (errors in function |
//| values) array[0..NPoints-1], S[i]>0. |
//| NPoints - training set size, NPoints>NVars+1 |
//| NVars - number of independent variables |
//| OUTPUT PARAMETERS: |
//| Info - return code: |
//| * -255, in case of unknown internal error |
//| * -4, if internal SVD subroutine haven't |
//| converged |
//| * -1, if incorrect parameters was passed |
//| (NPoints<NVars+2, NVars<1). |
//| * -2, if S[I]<=0 |
//| * 1, if subroutine successfully finished |
//| LM - linear model in the ALGLIB format. Use |
//| subroutines of this unit to work with the |
//| model. |
//| AR - additional results |
//+------------------------------------------------------------------+
static void CLinReg::LRBuildS(CMatrixDouble &xy,double &s[],const int npoints,
const int nvars,int &info,CLinearModel &lm,
CLRReport &ar)
{
//--- create variables
int i=0;
int j=0;
double v=0;
int offs=0;
double mean=0;
double variance=0;
double skewness=0;
double kurtosis=0;
int i_=0;
//--- creating arrays
double x[];
double means[];
double sigmas[];
//--- create array
CMatrixDouble xyi;
//--- initialization
info=0;
//--- Test parameters
if(npoints<=nvars+1 || nvars<1)
{
info=-1;
return;
}
//--- Copy data,add one more column (constant term)
xyi.Resize(npoints,nvars+2);
for(i=0;i<=npoints-1;i++)
{
for(i_=0;i_<=nvars-1;i_++)
xyi[i].Set(i_,xy[i][i_]);
xyi[i].Set(nvars,1);
xyi[i].Set(nvars+1,xy[i][nvars]);
}
//--- Standartization
ArrayResizeAL(x,npoints);
ArrayResizeAL(means,nvars);
ArrayResizeAL(sigmas,nvars);
for(j=0;j<=nvars-1;j++)
{
//--- copy
for(i_=0;i_<=npoints-1;i_++)
x[i_]=xy[i_][j];
//--- function call
CBaseStat::SampleMoments(x,npoints,mean,variance,skewness,kurtosis);
//--- change values
means[j]=mean;
sigmas[j]=MathSqrt(variance);
//--- check
if(sigmas[j]==0.0)
sigmas[j]=1;
//--- calculation
for(i=0;i<=npoints-1;i++)
xyi[i].Set(j,(xyi[i][j]-means[j])/sigmas[j]);
}
//--- Internal processing
LRInternal(xyi,s,npoints,nvars+1,info,lm,ar);
//--- check
if(info<0)
return;
//--- Un-standartization
offs=(int)MathRound(lm.m_w[3]);
for(j=0;j<=nvars-1;j++)
{
//--- Constant term is updated (and its covariance too,
//--- since it gets some variance from J-th component)
lm.m_w[offs+nvars]=lm.m_w[offs+nvars]-lm.m_w[offs+j]*means[j]/sigmas[j];
v=means[j]/sigmas[j];
for(i_=0;i_<=nvars;i_++)
ar.m_c[nvars].Set(i_,ar.m_c[nvars][i_]-v*ar.m_c[j][i_]);
for(i_=0;i_<=nvars;i_++)
ar.m_c[i_].Set(nvars,ar.m_c[i_][nvars]-v*ar.m_c[i_][j]);
//--- J-th term is updated
lm.m_w[offs+j]=lm.m_w[offs+j]/sigmas[j];
v=1/sigmas[j];
for(i_=0;i_<=nvars;i_++)
ar.m_c[j].Set(i_,v*ar.m_c[j][i_]);
for(i_=0;i_<=nvars;i_++)
ar.m_c[i_].Set(j,v*ar.m_c[i_][j]);
}
}
//+------------------------------------------------------------------+
//| Like LRBuildS, but builds model |
//| Y=A(0)*X[0] + ... + A(N-1)*X[N-1] |
//| i.m_e. with zero constant term. |
//+------------------------------------------------------------------+
static void CLinReg::LRBuildZS(CMatrixDouble &xy,double &s[],const int npoints,
const int nvars,int &info,CLinearModel &lm,
CLRReport &ar)
{
//--- create variables
int i=0;
int j=0;
double v=0;
int offs=0;
double mean=0;
double variance=0;
double skewness=0;
double kurtosis=0;
int i_=0;
//--- creating arrays
double x[];
double c[];
//--- create matrix
CMatrixDouble xyi;
//--- initialization
info=0;
//--- Test parameters
if(npoints<=nvars+1 || nvars<1)
{
info=-1;
return;
}
//--- Copy data,add one more column (constant term)
xyi.Resize(npoints,nvars+2);
for(i=0;i<=npoints-1;i++)
{
for(i_=0;i_<=nvars-1;i_++)
xyi[i].Set(i_,xy[i][i_]);
xyi[i].Set(nvars,0);
xyi[i].Set(nvars+1,xy[i][nvars]);
}
//--- Standartization: unusual scaling
ArrayResizeAL(x,npoints);
ArrayResizeAL(c,nvars);
for(j=0;j<=nvars-1;j++)
{
for(i_=0;i_<=npoints-1;i_++)
x[i_]=xy[i_][j];
//--- function call
CBaseStat::SampleMoments(x,npoints,mean,variance,skewness,kurtosis);
//--- check
if(MathAbs(mean)>MathSqrt(variance))
{
//--- variation is relatively small,it is better to
//--- bring mean value to 1
c[j]=mean;
}
else
{
//--- variation is large,it is better to bring variance to 1
if(variance==0.0)
variance=1;
c[j]=MathSqrt(variance);
}
for(i=0;i<=npoints-1;i++)
xyi[i].Set(j,xyi[i][j]/c[j]);
}
//--- Internal processing
LRInternal(xyi,s,npoints,nvars+1,info,lm,ar);
//--- check
if(info<0)
return;
//--- Un-standartization
offs=(int)MathRound(lm.m_w[3]);
for(j=0;j<=nvars-1;j++)
{
//--- J-th term is updated
lm.m_w[offs+j]=lm.m_w[offs+j]/c[j];
v=1/c[j];
for(i_=0;i_<=nvars;i_++)
ar.m_c[j].Set(i_,v*ar.m_c[j][i_]);
for(i_=0;i_<=nvars;i_++)
ar.m_c[i_].Set(j,v*ar.m_c[i_][j]);
}
}
//+------------------------------------------------------------------+
//| Like LRBuild but builds model |
//| Y=A(0)*X[0] + ... + A(N-1)*X[N-1] |
//| i.m_e. with zero constant term. |
//+------------------------------------------------------------------+
static void CLinReg::LRBuildZ(CMatrixDouble &xy,const int npoints,const int nvars,
int &info,CLinearModel &lm,CLRReport &ar)
{
//--- create variables
int i=0;
double sigma2=0;
int i_=0;
//--- create array
double s[];
//--- initialization
info=0;
//--- check
if(npoints<=nvars+1 || nvars<1)
{
info=-1;
return;
}
//--- allocation
ArrayResizeAL(s,npoints);
for(i=0;i<=npoints-1;i++)
s[i]=1;
//--- function call
LRBuildZS(xy,s,npoints,nvars,info,lm,ar);
//--- check
if(info<0)
return;
//--- calculation
sigma2=CMath::Sqr(ar.m_rmserror)*npoints/(npoints-nvars-1);
for(i=0;i<=nvars;i++)
{
for(i_=0;i_<=nvars;i_++)
ar.m_c[i].Set(i_,sigma2*ar.m_c[i][i_]);
}
}
//+------------------------------------------------------------------+
//| Unpacks coefficients of linear model. |
//| INPUT PARAMETERS: |
//| LM - linear model in ALGLIB format |
//| OUTPUT PARAMETERS: |
//| V - coefficients,array[0..NVars] |
//| constant term (intercept) is stored in the |
//| V[NVars]. |
//| NVars - number of independent variables (one less |
//| than number of coefficients) |
//+------------------------------------------------------------------+
static void CLinReg::LRUnpack(CLinearModel &lm,double &v[],int &nvars)
{
//--- create variables
int offs=0;
int i_=0;
int i1_=0;
//--- initialization
nvars=0;
//--- check
if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
return;
//--- change values
nvars=(int)MathRound(lm.m_w[2]);
offs=(int)MathRound(lm.m_w[3]);
//--- allocation
ArrayResizeAL(v,nvars+1);
//--- calculation
i1_=offs;
for(i_=0;i_<=nvars;i_++)
v[i_]=lm.m_w[i_+i1_];
}
//+------------------------------------------------------------------+
//| "Packs" coefficients and creates linear model in ALGLIB format |
//| (LRUnpack reversed). |
//| INPUT PARAMETERS: |
//| V - coefficients, array[0..NVars] |
//| NVars - number of independent variables |
//| OUTPUT PAREMETERS: |
//| LM - linear model. |
//+------------------------------------------------------------------+
static void CLinReg::LRPack(double &v[],const int nvars,CLinearModel &lm)
{
//--- create variables
int offs=0;
int i_=0;
int i1_=0;
//--- allocation
ArrayResizeAL(lm.m_w,5+nvars);
//--- change values
offs=4;
lm.m_w[0]=4+nvars+1;
lm.m_w[1]=m_lrvnum;
lm.m_w[2]=nvars;
lm.m_w[3]=offs;
//--- calculation
i1_=-offs;
for(i_=offs;i_<=offs+nvars;i_++)
lm.m_w[i_]=v[i_+i1_];
}
//+------------------------------------------------------------------+
//| Procesing |
//| INPUT PARAMETERS: |
//| LM - linear model |
//| X - input vector, array[0..NVars-1]. |
//| Result: |
//| value of linear model regression estimate |
//+------------------------------------------------------------------+
static double CLinReg::LRProcess(CLinearModel &lm,double &x[])
{
//--- create variables
double v=0;
int offs=0;
int nvars=0;
int i_=0;
int i1_=0;
//--- check
if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
return(EMPTY_VALUE);
//--- change values
nvars=(int)MathRound(lm.m_w[2]);
offs=(int)MathRound(lm.m_w[3]);
i1_=offs;
v=0.0;
//--- calculation
for(i_=0;i_<=nvars-1;i_++)
v+=x[i_]*lm.m_w[i_+i1_];
//--- return result
return(v+lm.m_w[offs+nvars]);
}
//+------------------------------------------------------------------+
//| RMS error on the test set |
//| INPUT PARAMETERS: |
//| LM - linear model |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| root mean square error. |
//+------------------------------------------------------------------+
static double CLinReg::LRRMSError(CLinearModel &lm,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double result=0;
int i=0;
double v=0;
int offs=0;
int nvars=0;
int i_=0;
int i1_=0;
//--- check
if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
return(EMPTY_VALUE);
//--- change values
nvars=(int)MathRound(lm.m_w[2]);
offs=(int)MathRound(lm.m_w[3]);
result=0;
//--- calculation
for(i=0;i<=npoints-1;i++)
{
i1_=offs;
v=0.0;
for(i_=0;i_<=nvars-1;i_++)
v+=xy[i][i_]*lm.m_w[i_+i1_];
v=v+lm.m_w[offs+nvars];
result=result+CMath::Sqr(v-xy[i][nvars]);
}
//--- return result
return(MathSqrt(result/npoints));
}
//+------------------------------------------------------------------+
//| Average error on the test set |
//| INPUT PARAMETERS: |
//| LM - linear model |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| average error. |
//+------------------------------------------------------------------+
static double CLinReg::LRAvgError(CLinearModel &lm,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double result=0;
int i=0;
double v=0;
int offs=0;
int nvars=0;
int i_=0;
int i1_=0;
//--- check
if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
return(EMPTY_VALUE);
//--- initialization
nvars=(int)MathRound(lm.m_w[2]);
offs=(int)MathRound(lm.m_w[3]);
result=0;
//--- calculation
for(i=0;i<=npoints-1;i++)
{
i1_=offs;
v=0.0;
for(i_=0;i_<=nvars-1;i_++)
v+=xy[i][i_]*lm.m_w[i_+i1_];
v=v+lm.m_w[offs+nvars];
result=result+MathAbs(v-xy[i][nvars]);
}
//--- return result
return(result/npoints);
}
//+------------------------------------------------------------------+
//| RMS error on the test set |
//| INPUT PARAMETERS: |
//| LM - linear model |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| average relative error. |
//+------------------------------------------------------------------+
static double CLinReg::LRAvgRelError(CLinearModel &lm,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double result=0;
int i=0;
int k=0;
double v=0;
int offs=0;
int nvars=0;
int i_=0;
int i1_=0;
//--- check
if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
return(EMPTY_VALUE);
//--- initialization
nvars=(int)MathRound(lm.m_w[2]);
offs=(int)MathRound(lm.m_w[3]);
result=0;
k=0;
//--- calculation
for(i=0;i<=npoints-1;i++)
{
//--- check
if(xy[i][nvars]!=0.0)
{
i1_=offs;
v=0.0;
for(i_=0;i_<=nvars-1;i_++)
v+=xy[i][i_]*lm.m_w[i_+i1_];
v=v+lm.m_w[offs+nvars];
//--- get result
result=result+MathAbs((v-xy[i][nvars])/xy[i][nvars]);
k=k+1;
}
}
//--- check
if(k!=0)
result=result/k;
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Copying of LinearModel strucure |
//| INPUT PARAMETERS: |
//| LM1 - original |
//| OUTPUT PARAMETERS: |
//| LM2 - copy |
//+------------------------------------------------------------------+
static void CLinReg::LRCopy(CLinearModel &lm1,CLinearModel &lm2)
{
//--- create variables
int k=0;
int i_=0;
//--- initialization
k=(int)MathRound(lm1.m_w[0]);
//--- allocation
ArrayResizeAL(lm2.m_w,k);
//--- copy
for(i_=0;i_<=k-1;i_++)
lm2.m_w[i_]=lm1.m_w[i_];
}
//+------------------------------------------------------------------+
//| Class method |
//+------------------------------------------------------------------+
static void CLinReg::LRLines(CMatrixDouble &xy,double &s[],const int n,
int &info,double &a,double &b,double &vara,
double &varb,double &covab,double &corrab,
double &p)
{
//--- create variables
int i=0;
double ss=0;
double sx=0;
double sxx=0;
double sy=0;
double stt=0;
double e1=0;
double e2=0;
double t=0;
double chi2=0;
//--- initialization
info=0;
a=0;
b=0;
vara=0;
varb=0;
covab=0;
corrab=0;
p=0;
//--- check
if(n<2)
{
info=-1;
return;
}
for(i=0;i<=n-1;i++)
{
//--- check
if((double)(s[i])<=0.0)
{
info=-2;
return;
}
}
//--- change value
info=1;
//--- Calculate S,SX,SY,SXX
ss=0;
sx=0;
sy=0;
sxx=0;
//--- calculation
for(i=0;i<=n-1;i++)
{
t=CMath::Sqr(s[i]);
ss=ss+1/t;
sx=sx+xy[i][0]/t;
sy=sy+xy[i][1]/t;
sxx=sxx+CMath::Sqr(xy[i][0])/t;
}
//--- Test for condition number
t=MathSqrt(4*CMath::Sqr(sx)+CMath::Sqr(ss-sxx));
e1=0.5*(ss+sxx+t);
e2=0.5*(ss+sxx-t);
//--- check
if(MathMin(e1,e2)<=1000*CMath::m_machineepsilon*MathMax(e1,e2))
{
info=-3;
return;
}
//--- Calculate A,B
a=0;
b=0;
stt=0;
//--- calculation
for(i=0;i<=n-1;i++)
{
t=(xy[i][0]-sx/ss)/s[i];
b=b+t*xy[i][1]/s[i];
stt=stt+CMath::Sqr(t);
}
b=b/stt;
a=(sy-sx*b)/ss;
//--- Calculate goodness-of-fit
if(n>2)
{
chi2=0;
for(i=0;i<=n-1;i++)
chi2=chi2+CMath::Sqr((xy[i][1]-a-b*xy[i][0])/s[i]);
//--- function call
p=CIncGammaF::IncompleteGammaC((double)(n-2)/(double)2,chi2/2);
}
else
p=1;
//--- Calculate other parameters
vara=(1+CMath::Sqr(sx)/(ss*stt))/ss;
varb=1/stt;
covab=-(sx/(ss*stt));
corrab=covab/MathSqrt(vara*varb);
}
//+------------------------------------------------------------------+
//| Class method |
//+------------------------------------------------------------------+
static void CLinReg::LRLine(CMatrixDouble &xy,const int n,int &info,
double &a,double &b)
{
//--- create variables
int i=0;
double vara=0;
double varb=0;
double covab=0;
double corrab=0;
double p=0;
//--- create array
double s[];
//--- initialization
info=0;
a=0;
b=0;
//--- check
if(n<2)
{
info=-1;
return;
}
//--- allocation
ArrayResizeAL(s,n);
for(i=0;i<=n-1;i++)
s[i]=1;
//--- function call
LRLines(xy,s,n,info,a,b,vara,varb,covab,corrab,p);
}
//+------------------------------------------------------------------+
//| Internal linear regression subroutine |
//+------------------------------------------------------------------+
static void CLinReg::LRInternal(CMatrixDouble &xy,double &s[],const int npoints,
const int nvars,int &info,CLinearModel &lm,
CLRReport &ar)
{
//--- create variables
int i=0;
int j=0;
int k=0;
int ncv=0;
int na=0;
int nacv=0;
double r=0;
double p=0;
double epstol=0;
int offs=0;
int i_=0;
int i1_=0;
//--- creating arrays
double b[];
double sv[];
double t[];
double svi[];
double work[];
//--- create matrix
CMatrixDouble a;
CMatrixDouble u;
CMatrixDouble vt;
CMatrixDouble vm;
CMatrixDouble xym;
//--- create objects of classes
CLRReport ar2;
CLinearModel tlm;
//--- initialization
info=0;
epstol=1000;
//--- Check for errors in data
if(npoints<nvars || nvars<1)
{
info=-1;
return;
}
for(i=0;i<=npoints-1;i++)
{
//--- check
if(s[i]<=0.0)
{
info=-2;
return;
}
}
//--- change value
info=1;
//--- Create design matrix
a.Resize(npoints,nvars);
ArrayResizeAL(b,npoints);
//--- calculation
for(i=0;i<=npoints-1;i++)
{
r=1/s[i];
for(i_=0;i_<=nvars-1;i_++)
a[i].Set(i_,r*xy[i][i_]);
b[i]=xy[i][nvars]/s[i];
}
//--- Allocate W:
//--- W[0] array size
//--- W[1] version number,0
//--- W[2] NVars (minus 1,to be compatible with external representation)
//--- W[3] coefficients offset
ArrayResizeAL(lm.m_w,4+nvars);
offs=4;
lm.m_w[0]=4+nvars;
lm.m_w[1]=m_lrvnum;
lm.m_w[2]=nvars-1;
lm.m_w[3]=offs;
//--- Solve problem using SVD:
//--- 0. check for degeneracy (different types)
//--- 1. A = U*diag(sv)*V'
//--- 2. T = b'*U
//--- 3. w = SUM((T[i]/sv[i])*V[..,i])
//--- 4. cov(wi,wj) = SUM(Vji*Vjk/sv[i]^2,K=1..M)
//--- see $15.4 of "Numerical Recipes in C" for more information
ArrayResizeAL(t,nvars);
ArrayResizeAL(svi,nvars);
ar.m_c.Resize(nvars,nvars);
vm.Resize(nvars,nvars);
//--- check
if(!CSingValueDecompose::RMatrixSVD(a,npoints,nvars,1,1,2,sv,u,vt))
{
info=-4;
return;
}
//--- check
if(sv[0]<=0.0)
{
//--- Degenerate case: zero design matrix.
for(i=offs;i<=offs+nvars-1;i++)
lm.m_w[i]=0;
//--- change values
ar.m_rmserror=LRRMSError(lm,xy,npoints);
ar.m_avgerror=LRAvgError(lm,xy,npoints);
ar.m_avgrelerror=LRAvgRelError(lm,xy,npoints);
ar.m_cvrmserror=ar.m_rmserror;
ar.m_cvavgerror=ar.m_avgerror;
ar.m_cvavgrelerror=ar.m_avgrelerror;
ar.m_ncvdefects=0;
//--- allocation
ArrayResizeAL(ar.m_cvdefects,nvars);
ar.m_c.Resize(nvars,nvars);
for(i=0;i<=nvars-1;i++)
{
for(j=0;j<=nvars-1;j++)
ar.m_c[i].Set(j,0);
}
//--- exit the function
return;
}
//--- check
if(sv[nvars-1]<=epstol*CMath::m_machineepsilon*sv[0])
{
//--- Degenerate case,non-zero design matrix.
//--- We can leave it and solve task in SVD least squares fashion.
//--- Solution and covariance matrix will be obtained correctly,
//--- but CV error estimates - will not. It is better to reduce
//--- it to non-degenerate task and to obtain correct CV estimates.
for(k=nvars;k>=1;k--)
{
//--- check
if(sv[k-1]>epstol*CMath::m_machineepsilon*sv[0])
{
//--- Reduce
xym.Resize(npoints,k+1);
for(i=0;i<=npoints-1;i++)
{
for(j=0;j<=k-1;j++)
{
//--- calculation
r=0.0;
for(i_=0;i_<=nvars-1;i_++)
r+=xy[i][i_]*vt[j][i_];
xym[i].Set(j,r);
}
xym[i].Set(k,xy[i][nvars]);
}
//--- Solve
LRInternal(xym,s,npoints,k,info,tlm,ar2);
//--- check
if(info!=1)
return;
//--- Convert back to un-reduced format
for(j=0;j<=nvars-1;j++)
lm.m_w[offs+j]=0;
for(j=0;j<=k-1;j++)
{
r=tlm.m_w[offs+j];
i1_=-offs;
for(i_=offs;i_<=offs+nvars-1;i_++)
lm.m_w[i_]=lm.m_w[i_]+r*vt[j][i_+i1_];
}
//--- change values
ar.m_rmserror=ar2.m_rmserror;
ar.m_avgerror=ar2.m_avgerror;
ar.m_avgrelerror=ar2.m_avgrelerror;
ar.m_cvrmserror=ar2.m_cvrmserror;
ar.m_cvavgerror=ar2.m_cvavgerror;
ar.m_cvavgrelerror=ar2.m_cvavgrelerror;
ar.m_ncvdefects=ar2.m_ncvdefects;
//--- allocation
ArrayResizeAL(ar.m_cvdefects,nvars);
for(j=0;j<=ar.m_ncvdefects-1;j++)
ar.m_cvdefects[j]=ar2.m_cvdefects[j];
//--- allocation
ar.m_c.Resize(nvars,nvars);
ArrayResizeAL(work,nvars+1);
//--- function calls
CBlas::MatrixMatrixMultiply(ar2.m_c,0,k-1,0,k-1,false,vt,0,k-1,0,nvars-1,false,1.0,vm,0,k-1,0,nvars-1,0.0,work);
CBlas::MatrixMatrixMultiply(vt,0,k-1,0,nvars-1,true,vm,0,k-1,0,nvars-1,false,1.0,ar.m_c,0,nvars-1,0,nvars-1,0.0,work);
//--- exit the function
return;
}
}
//--- change value
info=-255;
//--- exit the function
return;
}
//--- change values
for(i=0;i<=nvars-1;i++)
{
//--- check
if(sv[i]>epstol*CMath::m_machineepsilon*sv[0])
svi[i]=1/sv[i];
else
svi[i]=0;
}
//--- change values
for(i=0;i<=nvars-1;i++)
t[i]=0;
//--- change values
for(i=0;i<=npoints-1;i++)
{
r=b[i];
for(i_=0;i_<=nvars-1;i_++)
t[i_]=t[i_]+r*u[i][i_];
}
for(i=0;i<=nvars-1;i++)
lm.m_w[offs+i]=0;
//--- calculation
for(i=0;i<=nvars-1;i++)
{
r=t[i]*svi[i];
i1_=-offs;
for(i_=offs;i_<=offs+nvars-1;i_++)
lm.m_w[i_]=lm.m_w[i_]+r*vt[i][i_+i1_];
}
//--- calculation
for(j=0;j<=nvars-1;j++)
{
r=svi[j];
for(i_=0;i_<=nvars-1;i_++)
vm[i_].Set(j,r*vt[j][i_]);
}
//--- calculation
for(i=0;i<=nvars-1;i++)
{
for(j=i;j<=nvars-1;j++)
{
r=0.0;
for(i_=0;i_<=nvars-1;i_++)
r+=vm[i][i_]*vm[j][i_];
ar.m_c[i].Set(j,r);
ar.m_c[j].Set(i,r);
}
}
//--- Leave-1-out cross-validation error.
//--- NOTATIONS:
//--- A design matrix
//--- A*x = b original linear least squares task
//--- U*S*V' SVD of A
//--- ai i-th row of the A
//--- bi i-th element of the b
//--- xf solution of the original LLS task
//--- Cross-validation error of i-th element from a sample is
//--- calculated using following formula:
//--- ERRi = ai*xf - (ai*xf-bi*(ui*ui'))/(1-ui*ui') (1)
//--- This formula can be derived from normal equations of the
//--- original task
//--- (A'*A)x = A'*b (2)
//--- by applying modification (zeroing out i-th row of A) to (2):
//--- (A-ai)'*(A-ai) = (A-ai)'*b
//--- and using Sherman-Morrison formula for updating matrix inverse
//--- NOTE 1: b is not zeroed out since it is much simpler and
//--- does not influence final result.
//--- NOTE 2: some design matrices A have such ui that 1-ui*ui'=0.
//--- Formula (1) can't be applied for such cases and they are skipped
//--- from CV calculation (which distorts resulting CV estimate).
//--- But from the properties of U we can conclude that there can
//--- be no more than NVars such vectors. Usually
//--- NVars << NPoints, so in a normal case it only slightly
//--- influences result.
ncv=0;
na=0;
nacv=0;
ar.m_rmserror=0;
ar.m_avgerror=0;
ar.m_avgrelerror=0;
ar.m_cvrmserror=0;
ar.m_cvavgerror=0;
ar.m_cvavgrelerror=0;
ar.m_ncvdefects=0;
//--- allocation
ArrayResizeAL(ar.m_cvdefects,nvars);
for(i=0;i<=npoints-1;i++)
{
//--- Error on a training set
i1_=offs;
r=0.0;
for(i_=0;i_<=nvars-1;i_++)
r+=xy[i][i_]*lm.m_w[i_+i1_];
//--- change values
ar.m_rmserror=ar.m_rmserror+CMath::Sqr(r-xy[i][nvars]);
ar.m_avgerror=ar.m_avgerror+MathAbs(r-xy[i][nvars]);
//--- check
if(xy[i][nvars]!=0.0)
{
ar.m_avgrelerror=ar.m_avgrelerror+MathAbs((r-xy[i][nvars])/xy[i][nvars]);
na=na+1;
}
//--- Error using fast leave-one-out cross-validation
p=0.0;
for(i_=0;i_<=nvars-1;i_++)
p+=u[i][i_]*u[i][i_];
//--- check
if(p>1-epstol*CMath::m_machineepsilon)
{
ar.m_cvdefects[ar.m_ncvdefects]=i;
ar.m_ncvdefects=ar.m_ncvdefects+1;
continue;
}
//--- change values
r=s[i]*(r/s[i]-b[i]*p)/(1-p);
ar.m_cvrmserror=ar.m_cvrmserror+CMath::Sqr(r-xy[i][nvars]);
ar.m_cvavgerror=ar.m_cvavgerror+MathAbs(r-xy[i][nvars]);
//--- check
if(xy[i][nvars]!=0.0)
{
ar.m_cvavgrelerror=ar.m_cvavgrelerror+MathAbs((r-xy[i][nvars])/xy[i][nvars]);
nacv=nacv+1;
}
ncv=ncv+1;
}
//--- check
if(ncv==0)
{
//--- Something strange: ALL ui are degenerate.
//--- Unexpected...
info=-255;
//--- exit the function
return;
}
//--- change values
ar.m_rmserror=MathSqrt(ar.m_rmserror/npoints);
ar.m_avgerror=ar.m_avgerror/npoints;
//--- check
if(na!=0)
ar.m_avgrelerror=ar.m_avgrelerror/na;
ar.m_cvrmserror=MathSqrt(ar.m_cvrmserror/ncv);
ar.m_cvavgerror=ar.m_cvavgerror/ncv;
//--- check
if(nacv!=0)
ar.m_cvavgrelerror=ar.m_cvavgrelerror/nacv;
}
//+------------------------------------------------------------------+
//| Auxiliary class for CMLPBase |
//+------------------------------------------------------------------+
class CMultilayerPerceptron
{
public:
//--- variables
int m_hlnetworktype;
int m_hlnormtype;
//--- arrays
int m_hllayersizes[];
int m_hlconnections[];
int m_hlneurons[];
int m_structinfo[];
double m_weights[];
double m_columnmeans[];
double m_columnsigmas[];
double m_neurons[];
double m_dfdnet[];
double m_derror[];
double m_x[];
double m_y[];
double m_nwbuf[];
int m_integerbuf[];
//--- matrix
CMatrixDouble m_chunks;
//--- constructor, destructor
CMultilayerPerceptron(void);
~CMultilayerPerceptron(void);
//--- copy
void Copy(CMultilayerPerceptron &obj);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMultilayerPerceptron::CMultilayerPerceptron(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMultilayerPerceptron::~CMultilayerPerceptron(void)
{
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CMultilayerPerceptron::Copy(CMultilayerPerceptron &obj)
{
//--- copy variables
m_hlnetworktype=obj.m_hlnetworktype;
m_hlnormtype=obj.m_hlnormtype;
//--- copy arrays
ArrayCopy(m_hllayersizes,obj.m_hllayersizes);
ArrayCopy(m_hlconnections,obj.m_hlconnections);
ArrayCopy(m_hlneurons,obj.m_hlneurons);
ArrayCopy(m_structinfo,obj.m_structinfo);
ArrayCopy(m_weights,obj.m_weights);
ArrayCopy(m_columnmeans,obj.m_columnmeans);
ArrayCopy(m_columnsigmas,obj.m_columnsigmas);
ArrayCopy(m_neurons,obj.m_neurons);
ArrayCopy(m_dfdnet,obj.m_dfdnet);
ArrayCopy(m_derror,obj.m_derror);
ArrayCopy(m_x,obj.m_x);
ArrayCopy(m_y,obj.m_y);
ArrayCopy(m_nwbuf,obj.m_nwbuf);
ArrayCopy(m_integerbuf,obj.m_integerbuf);
//--- copy matrix
m_chunks=obj.m_chunks;
}
//+------------------------------------------------------------------+
//| This class is a shell for class CMultilayerPerceptron |
//+------------------------------------------------------------------+
class CMultilayerPerceptronShell
{
private:
CMultilayerPerceptron m_innerobj;
public:
//--- constructors, destructor
CMultilayerPerceptronShell(void);
CMultilayerPerceptronShell(CMultilayerPerceptron &obj);
~CMultilayerPerceptronShell(void);
//--- method
CMultilayerPerceptron *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMultilayerPerceptronShell::CMultilayerPerceptronShell(void)
{
}
//+------------------------------------------------------------------+
//| Copy constructor |
//+------------------------------------------------------------------+
CMultilayerPerceptronShell::CMultilayerPerceptronShell(CMultilayerPerceptron &obj)
{
//--- copy
m_innerobj.Copy(obj);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMultilayerPerceptronShell::~CMultilayerPerceptronShell(void)
{
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CMultilayerPerceptron *CMultilayerPerceptronShell::GetInnerObj(void)
{
//--- return result
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Multilayer perceptron class |
//+------------------------------------------------------------------+
class CMLPBase
{
private:
//--- private methods
static void AddInputLayer(const int ncount,int &lsizes[],int &ltypes[],int &lconnfirst[],int &lconnlast[],int &lastproc);
static void AddBiasedSummatorLayer(const int ncount,int &lsizes[],int &ltypes[],int &lconnfirst[],int &lconnlast[],int &lastproc);
static void AddActivationLayer(const int functype,int &lsizes[],int &ltypes[],int &lconnfirst[],int &lconnlast[],int &lastproc);
static void AddZeroLayer(int &lsizes[],int &ltypes[],int &lconnfirst[],int &lconnlast[],int &lastproc);
static void HLAddInputLayer(CMultilayerPerceptron &network,int &connidx,int &neuroidx,int &structinfoidx,int nin);
static void HLAddOutputLayer(CMultilayerPerceptron &network,int &connidx,int &neuroidx,int &structinfoidx,int &weightsidx,const int k,const int nprev,const int nout,const bool iscls,const bool islinearout);
static void HLAddHiddenLayer(CMultilayerPerceptron &network,int &connidx,int &neuroidx,int &structinfoidx,int &weightsidx,const int k,const int nprev,const int ncur);
static void FillHighLevelInformation(CMultilayerPerceptron &network,const int nin,const int nhid1,const int nhid2,const int nout,const bool iscls,const bool islinearout);
static void MLPCreate(const int nin,const int nout,int &lsizes[],int &ltypes[],int &lconnfirst[],int &lconnlast[],const int layerscount,const bool isclsnet,CMultilayerPerceptron &network);
static void MLPHessianBatchInternal(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,const bool naturalerr,double &e,double &grad[],CMatrixDouble &h);
static void MLPInternalCalculateGradient(CMultilayerPerceptron &network,double &neurons[],double &weights[],double &derror[],double &grad[],const bool naturalerrorfunc);
static void MLPChunkedGradient(CMultilayerPerceptron &network,CMatrixDouble &xy,const int cstart,const int csize,double &e,double &grad[],const bool naturalerrorfunc);
static double SafeCrossEntropy(const double t,const double z);
public:
//--- variables
static const int m_mlpvnum;
static const int m_mlpfirstversion;
static const int m_nfieldwidth;
static const int m_hlconm_nfieldwidth;
static const int m_hlm_nfieldwidth;
static const int m_chunksize;
//--- constructor, destructor
CMLPBase(void);
~CMLPBase(void);
//--- public methods
static void MLPCreate0(const int nin,const int nout,CMultilayerPerceptron &network);
static void MLPCreate1(const int nin,const int nhid,const int nout,CMultilayerPerceptron &network);
static void MLPCreate2(const int nin,const int nhid1,const int nhid2,const int nout,CMultilayerPerceptron &network);
static void MLPCreateB0(const int nin,const int nout,const double b,double d,CMultilayerPerceptron &network);
static void MLPCreateB1(const int nin,const int nhid,const int nout,const double b,double d,CMultilayerPerceptron &network);
static void MLPCreateB2(const int nin,const int nhid1,const int nhid2,const int nout,const double b,double d,CMultilayerPerceptron &network);
static void MLPCreateR0(const int nin,const int nout,const double a,const double b,CMultilayerPerceptron &network);
static void MLPCreateR1(const int nin,const int nhid,const int nout,const double a,const double b,CMultilayerPerceptron &network);
static void MLPCreateR2(const int nin,const int nhid1,const int nhid2,const int nout,const double a,const double b,CMultilayerPerceptron &network);
static void MLPCreateC0(const int nin,const int nout,CMultilayerPerceptron &network);
static void MLPCreateC1(const int nin,const int nhid,const int nout,CMultilayerPerceptron &network);
static void MLPCreateC2(const int nin,const int nhid1,const int nhid2,const int nout,CMultilayerPerceptron &network);
static void MLPCopy(CMultilayerPerceptron &network1,CMultilayerPerceptron &network2);
static void MLPSerializeOld(CMultilayerPerceptron &network,double &ra[],int &rlen);
static void MLPUnserializeOld(double &ra[],CMultilayerPerceptron &network);
static void MLPRandomize(CMultilayerPerceptron &network);
static void MLPRandomizeFull(CMultilayerPerceptron &network);
static void MLPInitPreprocessor(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize);
static void MLPProperties(CMultilayerPerceptron &network,int &nin,int &nout,int &wcount);
static bool MLPIsSoftMax(CMultilayerPerceptron &network);
static int MLPGetLayersCount(CMultilayerPerceptron &network);
static int MLPGetLayerSize(CMultilayerPerceptron &network,const int k);
static void MLPGetInputScaling(CMultilayerPerceptron &network,const int i,double &mean,double &sigma);
static void MLPGetOutputScaling(CMultilayerPerceptron &network,const int i,double &mean,double &sigma);
static void MLPGetNeuronInfo(CMultilayerPerceptron &network,const int k,const int i,int &fkind,double &threshold);
static double MLPGetWeight(CMultilayerPerceptron &network,const int k0,const int i0,const int k1,const int i1);
static void MLPSetInputScaling(CMultilayerPerceptron &network,const int i,const double mean,double sigma);
static void MLPSetOutputScaling(CMultilayerPerceptron &network,const int i,const double mean,double sigma);
static void MLPSetNeuronInfo(CMultilayerPerceptron &network,const int k,const int i,const int fkind,const double threshold);
static void MLPSetWeight(CMultilayerPerceptron &network,const int k0,const int i0,const int k1,const int i1,const double w);
static void MLPActivationFunction(double net,const int k,double &f,double &df,double &d2f);
static void MLPProcess(CMultilayerPerceptron &network,double &x[],double &y[]);
static void MLPProcessI(CMultilayerPerceptron &network,double &x[],double &y[]);
static double MLPError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize);
static double MLPErrorN(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize);
static int MLPClsError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize);
static double MLPRelClsError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
static double MLPAvgCE(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
static double MLPRMSError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
static double MLPAvgError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
static double MLPAvgRelError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
static void MLPGrad(CMultilayerPerceptron &network,double &x[],double &desiredy[],double &e,double &grad[]);
static void MLPGradN(CMultilayerPerceptron &network,double &x[],double &desiredy[],double &e,double &grad[]);
static void MLPGradBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,double &grad[]);
static void MLPGradNBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,double &grad[]);
static void MLPHessianNBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,double &grad[],CMatrixDouble &h);
static void MLPHessianBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,double &grad[],CMatrixDouble &h);
static void MLPInternalProcessVector(int &structinfo[],double &weights[],double &columnmeans[],double &columnsigmas[],double &neurons[],double &dfdnet[],double &x[],double &y[]);
static void MLPAlloc(CSerializer &s,CMultilayerPerceptron &network);
static void MLPSerialize(CSerializer &s,CMultilayerPerceptron &network);
static void MLPUnserialize(CSerializer &s,CMultilayerPerceptron &network);
};
//+------------------------------------------------------------------+
//| Initialize constants |
//+------------------------------------------------------------------+
const int CMLPBase::m_mlpvnum=7;
const int CMLPBase::m_mlpfirstversion=0;
const int CMLPBase::m_nfieldwidth=4;
const int CMLPBase::m_hlconm_nfieldwidth=5;
const int CMLPBase::m_hlm_nfieldwidth=4;
const int CMLPBase::m_chunksize=32;
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMLPBase::CMLPBase(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMLPBase::~CMLPBase(void)
{
}
//+------------------------------------------------------------------+
//| Creates neural network with NIn inputs, NOut outputs, |
//| without hidden layers, with linear output layer. Network weights |
//| are filled with small random values. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCreate0(const int nin,const int nout,
CMultilayerPerceptron &network)
{
//--- create variables
int layerscount=0;
int lastproc=0;
//--- creating arrays
int lsizes[];
int ltypes[];
int lconnfirst[];
int lconnlast[];
//--- initialization
layerscount=4;
//--- Allocate arrays
ArrayResizeAL(lsizes,layerscount);
ArrayResizeAL(ltypes,layerscount);
ArrayResizeAL(lconnfirst,layerscount);
ArrayResizeAL(lconnlast,layerscount);
//--- Layers
AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(-5,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
FillHighLevelInformation(network,nin,0,0,nout,false,true);
}
//+------------------------------------------------------------------+
//| Same as MLPCreate0, but with one hidden layer (NHid neurons) with|
//| non-linear activation function. Output layer is linear. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCreate1(const int nin,const int nhid,const int nout,
CMultilayerPerceptron &network)
{
//--- create variables
int layerscount=0;
int lastproc=0;
//--- creating arrays
int lsizes[];
int ltypes[];
int lconnfirst[];
int lconnlast[];
//--- create variables
layerscount=7;
//--- Allocate arrays
ArrayResizeAL(lsizes,layerscount);
ArrayResizeAL(ltypes,layerscount);
ArrayResizeAL(lconnfirst,layerscount);
ArrayResizeAL(lconnlast,layerscount);
//--- Layers
AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nhid,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(-5,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
FillHighLevelInformation(network,nin,nhid,0,nout,false,true);
}
//+------------------------------------------------------------------+
//| Same as MLPCreate0,but with two hidden layers (NHid1 and NHid2 |
//| neurons) with non-linear activation function. Output layer is |
//| linear. |
//| $ALL |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCreate2(const int nin,const int nhid1,const int nhid2,
const int nout,CMultilayerPerceptron &network)
{
//--- create variables
int layerscount=0;
int lastproc=0;
//--- creating arrays
int lsizes[];
int ltypes[];
int lconnfirst[];
int lconnlast[];
//--- initialization
layerscount=10;
//--- Allocate arrays
ArrayResizeAL(lsizes,layerscount);
ArrayResizeAL(ltypes,layerscount);
ArrayResizeAL(lconnfirst,layerscount);
ArrayResizeAL(lconnlast,layerscount);
//--- Layers
AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nhid1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nhid2,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(-5,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
FillHighLevelInformation(network,nin,nhid1,nhid2,nout,false,true);
}
//+------------------------------------------------------------------+
//| Creates neural network with NIn inputs, NOut outputs, without |
//| hidden layers with non-linear output layer. Network weights are |
//| filled with small random values. |
//| Activation function of the output layer takes values: |
//| (B, +INF), if D>=0 |
//| or |
//| (-INF, B), if D<0. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCreateB0(const int nin,const int nout,const double b,
double d,CMultilayerPerceptron &network)
{
//--- create variables
int layerscount=0;
int lastproc=0;
int i=0;
//--- creating arrays
int lsizes[];
int ltypes[];
int lconnfirst[];
int lconnlast[];
//--- initialization
layerscount=4;
//--- check
if(d>=0.0)
d=1;
else
d=-1;
//--- Allocate arrays
ArrayResizeAL(lsizes,layerscount);
ArrayResizeAL(ltypes,layerscount);
ArrayResizeAL(lconnfirst,layerscount);
ArrayResizeAL(lconnlast,layerscount);
//--- Layers
AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(3,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
FillHighLevelInformation(network,nin,0,0,nout,false,false);
//--- Turn on ouputs shift/scaling.
for(i=nin;i<=nin+nout-1;i++)
{
network.m_columnmeans[i]=b;
network.m_columnsigmas[i]=d;
}
}
//+------------------------------------------------------------------+
//| Same as MLPCreateB0 but with non-linear hidden layer. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCreateB1(const int nin,const int nhid,const int nout,
const double b,double d,CMultilayerPerceptron &network)
{
//--- create variables
int layerscount=0;
int lastproc=0;
int i=0;
//--- creating arrays
int lsizes[];
int ltypes[];
int lconnfirst[];
int lconnlast[];
layerscount=7;
//--- check
if(d>=0.0)
d=1;
else
d=-1;
//--- Allocate arrays
ArrayResizeAL(lsizes,layerscount);
ArrayResizeAL(ltypes,layerscount);
ArrayResizeAL(lconnfirst,layerscount);
ArrayResizeAL(lconnlast,layerscount);
//--- Layers
AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nhid,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(3,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
FillHighLevelInformation(network,nin,nhid,0,nout,false,false);
//--- Turn on ouputs shift/scaling.
for(i=nin;i<=nin+nout-1;i++)
{
network.m_columnmeans[i]=b;
network.m_columnsigmas[i]=d;
}
}
//+------------------------------------------------------------------+
//| Same as MLPCreateB0 but with two non-linear hidden layers. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCreateB2(const int nin,const int nhid1,const int nhid2,
const int nout,const double b,double d,
CMultilayerPerceptron &network)
{
//--- create variables
int layerscount=0;
int lastproc=0;
int i=0;
//--- creating arrays
int lsizes[];
int ltypes[];
int lconnfirst[];
int lconnlast[];
//--- initialization
layerscount=10;
//--- check
if(d>=0.0)
d=1;
else
d=-1;
//--- Allocate arrays
ArrayResizeAL(lsizes,layerscount);
ArrayResizeAL(ltypes,layerscount);
ArrayResizeAL(lconnfirst,layerscount);
ArrayResizeAL(lconnlast,layerscount);
//--- Layers
AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nhid1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nhid2,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(3,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
FillHighLevelInformation(network,nin,nhid1,nhid2,nout,false,false);
//--- Turn on ouputs shift/scaling.
for(i=nin;i<=nin+nout-1;i++)
{
network.m_columnmeans[i]=b;
network.m_columnsigmas[i]=d;
}
}
//+------------------------------------------------------------------+
//| Creates neural network with NIn inputs, NOut outputs, |
//| without hidden layers with non-linear output layer. Network |
//| weights are filled with small random values. Activation function |
//| of the output layer takes values [A,B]. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCreateR0(const int nin,const int nout,const double a,
const double b,CMultilayerPerceptron &network)
{
//--- create variables
int layerscount=0;
int lastproc=0;
int i=0;
//--- creating arrays
int lsizes[];
int ltypes[];
int lconnfirst[];
int lconnlast[];
//--- initialization
layerscount=1+3;
//--- Allocate arrays
ArrayResizeAL(lsizes,layerscount);
ArrayResizeAL(ltypes,layerscount);
ArrayResizeAL(lconnfirst,layerscount);
ArrayResizeAL(lconnlast,layerscount);
//--- Layers
AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
FillHighLevelInformation(network,nin,0,0,nout,false,false);
//--- Turn on outputs shift/scaling.
for(i=nin;i<=nin+nout-1;i++)
{
network.m_columnmeans[i]=0.5*(a+b);
network.m_columnsigmas[i]=0.5*(a-b);
}
}
//+------------------------------------------------------------------+
//| Same as MLPCreateR0,but with non-linear hidden layer. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCreateR1(const int nin,const int nhid,const int nout,
const double a,const double b,
CMultilayerPerceptron &network)
{
//--- create variables
int layerscount=0;
int lastproc=0;
int i=0;
//--- creating arrays
int lsizes[];
int ltypes[];
int lconnfirst[];
int lconnlast[];
//--- initialization
layerscount=7;
//--- Allocate arrays
ArrayResizeAL(lsizes,layerscount);
ArrayResizeAL(ltypes,layerscount);
ArrayResizeAL(lconnfirst,layerscount);
ArrayResizeAL(lconnlast,layerscount);
//--- Layers
AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nhid,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
FillHighLevelInformation(network,nin,nhid,0,nout,false,false);
//--- Turn on outputs shift/scaling.
for(i=nin;i<=nin+nout-1;i++)
{
network.m_columnmeans[i]=0.5*(a+b);
network.m_columnsigmas[i]=0.5*(a-b);
}
}
//+------------------------------------------------------------------+
//| Same as MLPCreateR0,but with two non-linear hidden layers. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCreateR2(const int nin,const int nhid1,const int nhid2,
const int nout,const double a,const double b,
CMultilayerPerceptron &network)
{
//--- create variables
int layerscount=0;
int lastproc=0;
int i=0;
//--- creating arrays
int lsizes[];
int ltypes[];
int lconnfirst[];
int lconnlast[];
//--- initialization
layerscount=10;
//--- Allocate arrays
ArrayResizeAL(lsizes,layerscount);
ArrayResizeAL(ltypes,layerscount);
ArrayResizeAL(lconnfirst,layerscount);
ArrayResizeAL(lconnlast,layerscount);
//--- Layers
AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nhid1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nhid2,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nout,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,false,network);
//--- function call
FillHighLevelInformation(network,nin,nhid1,nhid2,nout,false,false);
//--- Turn on outputs shift/scaling.
for(i=nin;i<=nin+nout-1;i++)
{
network.m_columnmeans[i]=0.5*(a+b);
network.m_columnsigmas[i]=0.5*(a-b);
}
}
//+------------------------------------------------------------------+
//| Creates classifier network with NIn inputs and NOut possible |
//| classes. |
//| Network contains no hidden layers and linear output layer with |
//| SOFTMAX-normalization (so outputs sums up to 1.0 and converge to |
//| posterior probabilities). |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCreateC0(const int nin,const int nout,
CMultilayerPerceptron &network)
{
//--- create variables
int layerscount=0;
int lastproc=0;
//--- creating arrays
int lsizes[];
int ltypes[];
int lconnfirst[];
int lconnlast[];
//--- check
if(!CAp::Assert(nout>=2,__FUNCTION__+": NOut<2!"))
return;
//--- initialization
layerscount=4;
//--- Allocate arrays
ArrayResizeAL(lsizes,layerscount);
ArrayResizeAL(ltypes,layerscount);
ArrayResizeAL(lconnfirst,layerscount);
ArrayResizeAL(lconnlast,layerscount);
//--- Layers
AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nout-1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddZeroLayer(lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,true,network);
//--- function call
FillHighLevelInformation(network,nin,0,0,nout,true,true);
}
//+------------------------------------------------------------------+
//| Same as MLPCreateC0,but with one non-linear hidden layer. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCreateC1(const int nin,const int nhid,const int nout,
CMultilayerPerceptron &network)
{
//--- create variables
int layerscount=0;
int lastproc=0;
//--- creating arrays
int lsizes[];
int ltypes[];
int lconnfirst[];
int lconnlast[];
//--- check
if(!CAp::Assert(nout>=2,__FUNCTION__+": NOut<2!"))
return;
//--- initialization
layerscount=7;
//--- Allocate arrays
ArrayResizeAL(lsizes,layerscount);
ArrayResizeAL(ltypes,layerscount);
ArrayResizeAL(lconnfirst,layerscount);
ArrayResizeAL(lconnlast,layerscount);
//--- Layers
AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nhid,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nout-1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddZeroLayer(lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,true,network);
//--- function call
FillHighLevelInformation(network,nin,nhid,0,nout,true,true);
}
//+------------------------------------------------------------------+
//| Same as MLPCreateC0, but with two non-linear hidden layers. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCreateC2(const int nin,const int nhid1,const int nhid2,
const int nout,CMultilayerPerceptron &network)
{
//--- create variables
int layerscount=0;
int lastproc=0;
//--- creating arrays
int lsizes[];
int ltypes[];
int lconnfirst[];
int lconnlast[];
//--- check
if(!CAp::Assert(nout>=2,__FUNCTION__+": NOut<2!"))
return;
//--- initialization
layerscount=10;
//--- Allocate arrays
ArrayResizeAL(lsizes,layerscount);
ArrayResizeAL(ltypes,layerscount);
ArrayResizeAL(lconnfirst,layerscount);
ArrayResizeAL(lconnlast,layerscount);
//--- Layers
AddInputLayer(nin,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nhid1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nhid2,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddActivationLayer(1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddBiasedSummatorLayer(nout-1,lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- function call
AddZeroLayer(lsizes,ltypes,lconnfirst,lconnlast,lastproc);
//--- Create
MLPCreate(nin,nout,lsizes,ltypes,lconnfirst,lconnlast,layerscount,true,network);
//--- function call
FillHighLevelInformation(network,nin,nhid1,nhid2,nout,true,true);
}
//+------------------------------------------------------------------+
//| Copying of neural network |
//| INPUT PARAMETERS: |
//| Network1 - original |
//| OUTPUT PARAMETERS: |
//| Network2 - copy |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCopy(CMultilayerPerceptron &network1,
CMultilayerPerceptron &network2)
{
//--- copy
network2.m_hlnetworktype=network1.m_hlnetworktype;
network2.m_hlnormtype=network1.m_hlnormtype;
//--- function calls
CApServ::CopyIntegerArray(network1.m_hllayersizes,network2.m_hllayersizes);
CApServ::CopyIntegerArray(network1.m_hlconnections,network2.m_hlconnections);
CApServ::CopyIntegerArray(network1.m_hlneurons,network2.m_hlneurons);
CApServ::CopyIntegerArray(network1.m_structinfo,network2.m_structinfo);
CApServ::CopyRealArray(network1.m_weights,network2.m_weights);
CApServ::CopyRealArray(network1.m_columnmeans,network2.m_columnmeans);
CApServ::CopyRealArray(network1.m_columnsigmas,network2.m_columnsigmas);
CApServ::CopyRealArray(network1.m_neurons,network2.m_neurons);
CApServ::CopyRealArray(network1.m_dfdnet,network2.m_dfdnet);
CApServ::CopyRealArray(network1.m_derror,network2.m_derror);
CApServ::CopyRealArray(network1.m_x,network2.m_x);
CApServ::CopyRealArray(network1.m_y,network2.m_y);
CApServ::CopyRealMatrix(network1.m_chunks,network2.m_chunks);
CApServ::CopyRealArray(network1.m_nwbuf,network2.m_nwbuf);
CApServ::CopyIntegerArray(network1.m_integerbuf,network2.m_integerbuf);
}
//+------------------------------------------------------------------+
//| Serialization of MultiLayerPerceptron strucure |
//| INPUT PARAMETERS: |
//| Network - original |
//| OUTPUT PARAMETERS: |
//| RA - array of real numbers which stores network, |
//| array[0..RLen-1] |
//| RLen - RA lenght |
//+------------------------------------------------------------------+
static void CMLPBase::MLPSerializeOld(CMultilayerPerceptron &network,
double &ra[],int &rlen)
{
//--- create variables
int i=0;
int ssize=0;
int ntotal=0;
int nin=0;
int nout=0;
int wcount=0;
int sigmalen=0;
int offs=0;
int i_=0;
int i1_=0;
//--- initialization
rlen=0;
//--- Unload info
ssize=network.m_structinfo[0];
nin=network.m_structinfo[1];
nout=network.m_structinfo[2];
ntotal=network.m_structinfo[3];
wcount=network.m_structinfo[4];
//--- check
if(MLPIsSoftMax(network))
sigmalen=nin;
else
sigmalen=nin+nout;
//--- RA format:
//--- LEN DESRC.
//--- 1 RLen
//--- 1 version (MLPVNum)
//--- 1 StructInfo size
//--- SSize StructInfo
//--- WCount Weights
//--- SigmaLen ColumnMeans
//--- SigmaLen ColumnSigmas
rlen=3+ssize+wcount+2*sigmalen;
//--- allocation
ArrayResizeAL(ra,rlen);
//--- change values
ra[0]=rlen;
ra[1]=m_mlpvnum;
ra[2]=ssize;
//--- calculation
offs=3;
for(i=0;i<=ssize-1;i++)
ra[offs+i]=network.m_structinfo[i];
//--- calculation
offs=offs+ssize;
i1_=-offs;
for(i_=offs;i_<=offs+wcount-1;i_++)
ra[i_]=network.m_weights[i_+i1_];
//--- calculation
offs=offs+wcount;
i1_=-offs;
for(i_=offs;i_<=offs+sigmalen-1;i_++)
ra[i_]=network.m_columnmeans[i_+i1_];
//--- calculation
offs=offs+sigmalen;
i1_=-offs;
for(i_=offs;i_<=offs+sigmalen-1;i_++)
ra[i_]=network.m_columnsigmas[i_+i1_];
offs=offs+sigmalen;
}
//+------------------------------------------------------------------+
//| Unserialization of MultiLayerPerceptron strucure |
//| INPUT PARAMETERS: |
//| RA - real array which stores network |
//| OUTPUT PARAMETERS: |
//| Network - restored network |
//+------------------------------------------------------------------+
static void CMLPBase::MLPUnserializeOld(double &ra[],CMultilayerPerceptron &network)
{
//--- create variables
int i=0;
int ssize=0;
int ntotal=0;
int nin=0;
int nout=0;
int wcount=0;
int sigmalen=0;
int offs=0;
int i_=0;
int i1_=0;
//--- check
if(!CAp::Assert((int)MathRound(ra[1])==m_mlpvnum,__FUNCTION__+": incorrect array!"))
return;
//--- Unload StructInfo from IA
offs=3;
ssize=(int)MathRound(ra[2]);
//--- allocation
ArrayResizeAL(network.m_structinfo,ssize);
for(i=0;i<=ssize-1;i++)
network.m_structinfo[i]=(int)MathRound(ra[offs+i]);
offs=offs+ssize;
//--- Unload info from StructInfo
ssize=network.m_structinfo[0];
nin=network.m_structinfo[1];
nout=network.m_structinfo[2];
ntotal=network.m_structinfo[3];
wcount=network.m_structinfo[4];
//--- check
if(network.m_structinfo[6]==0)
sigmalen=nin+nout;
else
sigmalen=nin;
//--- Allocate space for other fields
ArrayResizeAL(network.m_weights,wcount);
ArrayResizeAL(network.m_columnmeans,sigmalen);
ArrayResizeAL(network.m_columnsigmas,sigmalen);
ArrayResizeAL(network.m_neurons,ntotal);
network.m_chunks.Resize(3*ntotal+1,m_chunksize);
ArrayResizeAL(network.m_nwbuf,MathMax(wcount,2*nout));
ArrayResizeAL(network.m_dfdnet,ntotal);
ArrayResizeAL(network.m_x,nin);
ArrayResizeAL(network.m_y,nout);
ArrayResizeAL(network.m_derror,ntotal);
//--- Copy parameters from RA
i1_=offs;
for(i_=0;i_<=wcount-1;i_++)
network.m_weights[i_]=ra[i_+i1_];
//--- calculation
offs=offs+wcount;
i1_=offs;
for(i_=0;i_<=sigmalen-1;i_++)
network.m_columnmeans[i_]=ra[i_+i1_];
//--- calculation
offs=offs+sigmalen;
i1_=offs;
for(i_=0;i_<=sigmalen-1;i_++)
network.m_columnsigmas[i_]=ra[i_+i1_];
offs=offs+sigmalen;
}
//+------------------------------------------------------------------+
//| Randomization of neural network weights |
//+------------------------------------------------------------------+
static void CMLPBase::MLPRandomize(CMultilayerPerceptron &network)
{
//--- create variables
int i=0;
int nin=0;
int nout=0;
int wcount=0;
//--- function call
MLPProperties(network,nin,nout,wcount);
//--- change values
for(i=0;i<=wcount-1;i++)
network.m_weights[i]=CMath::RandomReal()-0.5;
}
//+------------------------------------------------------------------+
//| Randomization of neural network weights and standartisator |
//+------------------------------------------------------------------+
static void CMLPBase::MLPRandomizeFull(CMultilayerPerceptron &network)
{
//--- create variables
int i=0;
int nin=0;
int nout=0;
int wcount=0;
int ntotal=0;
int istart=0;
int offs=0;
int ntype=0;
//--- function call
MLPProperties(network,nin,nout,wcount);
//--- initialization
ntotal=network.m_structinfo[3];
istart=network.m_structinfo[5];
//--- Process network
for(i=0;i<=wcount-1;i++)
network.m_weights[i]=CMath::RandomReal()-0.5;
for(i=0;i<=nin-1;i++)
{
network.m_columnmeans[i]=2*CMath::RandomReal()-1;
network.m_columnsigmas[i]=1.5*CMath::RandomReal()+0.5;
}
//--- check
if(!MLPIsSoftMax(network))
{
for(i=0;i<=nout-1;i++)
{
offs=istart+(ntotal-nout+i)*m_nfieldwidth;
ntype=network.m_structinfo[offs+0];
//--- check
if(ntype==0)
{
//--- Shifts are changed only for linear outputs neurons
network.m_columnmeans[nin+i]=2*CMath::RandomReal()-1;
}
//--- check
if(ntype==0 || ntype==3)
{
//--- Scales are changed only for linear or bounded outputs neurons.
//--- Note that scale randomization preserves sign.
network.m_columnsigmas[nin+i]=MathSign(network.m_columnsigmas[nin+i])*(1.5*CMath::RandomReal()+0.5);
}
}
}
}
//+------------------------------------------------------------------+
//| Internal subroutine. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPInitPreprocessor(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int ssize)
{
//--- create variables
int i=0;
int j=0;
int jmax=0;
int nin=0;
int nout=0;
int wcount=0;
int ntotal=0;
int istart=0;
int offs=0;
int ntype=0;
double s=0;
//--- creating arrays
double means[];
double sigmas[];
//--- function call
MLPProperties(network,nin,nout,wcount);
//--- initialization
ntotal=network.m_structinfo[3];
istart=network.m_structinfo[5];
//--- Means/Sigmas
if(MLPIsSoftMax(network))
jmax=nin-1;
else
jmax=nin+nout-1;
//--- allocation
ArrayResizeAL(means,jmax+1);
ArrayResizeAL(sigmas,jmax+1);
//--- calculation
for(j=0;j<=jmax;j++)
{
//--- means
means[j]=0;
for(i=0;i<=ssize-1;i++)
means[j]=means[j]+xy[i][j];
means[j]=means[j]/ssize;
//--- sigmas
sigmas[j]=0;
for(i=0;i<=ssize-1;i++)
sigmas[j]=sigmas[j]+CMath::Sqr(xy[i][j]-means[j]);
sigmas[j]=MathSqrt(sigmas[j]/ssize);
}
//--- Inputs
for(i=0;i<=nin-1;i++)
{
network.m_columnmeans[i]=means[i];
network.m_columnsigmas[i]=sigmas[i];
//--- check
if(network.m_columnsigmas[i]==0.0)
network.m_columnsigmas[i]=1;
}
//--- Outputs
if(!MLPIsSoftMax(network))
{
for(i=0;i<=nout-1;i++)
{
offs=istart+(ntotal-nout+i)*m_nfieldwidth;
ntype=network.m_structinfo[offs+0];
//--- Linear outputs
if(ntype==0)
{
network.m_columnmeans[nin+i]=means[nin+i];
network.m_columnsigmas[nin+i]=sigmas[nin+i];
//--- check
if(network.m_columnsigmas[nin+i]==0.0)
network.m_columnsigmas[nin+i]=1;
}
//--- Bounded outputs (half-interval)
if(ntype==3)
{
s=means[nin+i]-network.m_columnmeans[nin+i];
//--- check
if(s==0.0)
s=MathSign(network.m_columnsigmas[nin+i]);
//--- check
if(s==0.0)
s=1.0;
//--- change value
network.m_columnsigmas[nin+i]=MathSign(network.m_columnsigmas[nin+i])*MathAbs(s);
//--- check
if((double)(network.m_columnsigmas[nin+i])==0.0)
network.m_columnsigmas[nin+i]=1;
}
}
}
}
//+------------------------------------------------------------------+
//| Returns information about initialized network: number of inputs, |
//| outputs, weights. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPProperties(CMultilayerPerceptron &network,int &nin,
int &nout,int &wcount)
{
//--- change values
nin=network.m_structinfo[1];
nout=network.m_structinfo[2];
wcount=network.m_structinfo[4];
}
//+------------------------------------------------------------------+
//| Tells whether network is SOFTMAX-normalized (i.m_e. classifier) |
//| or not. |
//+------------------------------------------------------------------+
static bool CMLPBase::MLPIsSoftMax(CMultilayerPerceptron &network)
{
//--- check
if(network.m_structinfo[6]==1)
return(true);
//--- return result
return(false);
}
//+------------------------------------------------------------------+
//| This function returns total number of layers (including input, |
//| hidden and output layers). |
//+------------------------------------------------------------------+
static int CMLPBase::MLPGetLayersCount(CMultilayerPerceptron &network)
{
//--- return result
return(CAp::Len(network.m_hllayersizes));
}
//+------------------------------------------------------------------+
//| This function returns size of K-th layer. |
//| K=0 corresponds to input layer, K=CNT-1 corresponds to output |
//| layer. |
//| Size of the output layer is always equal to the number of |
//| outputs, although when we have softmax-normalized network, last |
//| neuron doesn't have any connections - it is just zero. |
//+------------------------------------------------------------------+
static int CMLPBase::MLPGetLayerSize(CMultilayerPerceptron &network,
const int k)
{
//--- check
if(!CAp::Assert(k>=0 && k<CAp::Len(network.m_hllayersizes),__FUNCTION__+": incorrect layer index"))
return(-1);
//--- return result
return(network.m_hllayersizes[k]);
}
//+------------------------------------------------------------------+
//| This function returns offset/scaling coefficients for I-th input |
//| of the network. |
//| INPUT PARAMETERS: |
//| Network - network |
//| I - input index |
//| OUTPUT PARAMETERS: |
//| Mean - mean term |
//| Sigma - sigma term,guaranteed to be nonzero. |
//| I-th input is passed through linear transformation |
//| IN[i]=(IN[i]-Mean)/Sigma |
//| before feeding to the network |
//+------------------------------------------------------------------+
static void CMLPBase::MLPGetInputScaling(CMultilayerPerceptron &network,
const int i,double &mean,
double &sigma)
{
//--- initialization
mean=0;
sigma=0;
//--- check
if(!CAp::Assert(i>=0 && i<network.m_hllayersizes[0],__FUNCTION__+": incorrect (nonexistent) I"))
return;
//--- change values
mean=network.m_columnmeans[i];
sigma=network.m_columnsigmas[i];
//--- check
if(sigma==0.0)
sigma=1;
}
//+------------------------------------------------------------------+
//| This function returns offset/scaling coefficients for I-th output|
//| of the network. |
//| INPUT PARAMETERS: |
//| Network - network |
//| I - input index |
//| OUTPUT PARAMETERS: |
//| Mean - mean term |
//| Sigma - sigma term, guaranteed to be nonzero. |
//| I-th output is passed through linear transformation |
//| OUT[i] = OUT[i]*Sigma+Mean |
//| before returning it to user. In case we have SOFTMAX-normalized |
//| network, we return (Mean,Sigma)=(0.0,1.0). |
//+------------------------------------------------------------------+
static void CMLPBase::MLPGetOutputScaling(CMultilayerPerceptron &network,
const int i,double &mean,
double &sigma)
{
//--- initialization
mean=0;
sigma=0;
//--- check
if(!CAp::Assert(i>=0 && i<network.m_hllayersizes[CAp::Len(network.m_hllayersizes)-1],__FUNCTION__+": incorrect (nonexistent) I"))
return;
//--- check
if(network.m_structinfo[6]==1)
{
//--- change values
mean=0;
sigma=1;
}
else
{
//--- change values
mean=network.m_columnmeans[network.m_hllayersizes[0]+i];
sigma=network.m_columnsigmas[network.m_hllayersizes[0]+i];
}
}
//+------------------------------------------------------------------+
//| This function returns information about Ith neuron of Kth layer |
//| INPUT PARAMETERS: |
//| Network - network |
//| K - layer index |
//| I - neuron index (within layer) |
//| OUTPUT PARAMETERS: |
//| FKind - activation function type (used by |
//| MLPActivationFunction()) this value is zero |
//| for input or linear neurons |
//| Threshold - also called offset, bias |
//| zero for input neurons |
//| NOTE: this function throws exception if layer or neuron with |
//| given index do not exists. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPGetNeuronInfo(CMultilayerPerceptron &network,
const int k,const int i,int &fkind,
double &threshold)
{
//--- create variables
int ncnt=0;
int istart=0;
int highlevelidx=0;
int activationoffset=0;
//--- initialization
fkind=0;
threshold=0;
ncnt=CAp::Len(network.m_hlneurons)/m_hlm_nfieldwidth;
istart=network.m_structinfo[5];
//--- search
network.m_integerbuf[0]=k;
network.m_integerbuf[1]=i;
//--- function call
highlevelidx=CApServ::RecSearch(network.m_hlneurons,m_hlm_nfieldwidth,2,0,ncnt,network.m_integerbuf);
//--- check
if(!CAp::Assert(highlevelidx>=0,__FUNCTION__+": incorrect (nonexistent) layer or neuron index"))
return;
//--- 1. find offset of the activation function record in the
if(network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+2]>=0)
{
activationoffset=istart+network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+2]*m_nfieldwidth;
fkind=network.m_structinfo[activationoffset+0];
}
else
fkind=0;
//--- check
if(network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+3]>=0)
threshold=network.m_weights[network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+3]];
else
threshold=0;
}
//+------------------------------------------------------------------+
//| This function returns information about connection from I0-th |
//| neuron of K0-th layer to I1-th neuron of K1-th layer. |
//| INPUT PARAMETERS: |
//| Network - network |
//| K0 - layer index |
//| I0 - neuron index (within layer) |
//| K1 - layer index |
//| I1 - neuron index (within layer) |
//| RESULT: |
//| connection weight (zero for non-existent connections) |
//| This function: |
//| 1. throws exception if layer or neuron with given index do not |
//| exists. |
//| 2. returns zero if neurons exist, but there is no connection |
//| between them |
//+------------------------------------------------------------------+
static double CMLPBase::MLPGetWeight(CMultilayerPerceptron &network,
const int k0,const int i0,
const int k1,const int i1)
{
//--- create variables
double result=0;
int ccnt=0;
int highlevelidx=0;
//--- initialization
ccnt=CAp::Len(network.m_hlconnections)/m_hlconm_nfieldwidth;
//--- check params
if(!CAp::Assert(k0>=0 && k0<CAp::Len(network.m_hllayersizes),__FUNCTION__+": incorrect (nonexistent) K0"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(i0>=0 && i0<network.m_hllayersizes[k0],__FUNCTION__+": incorrect (nonexistent) I0"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(k1>=0 && k1<CAp::Len(network.m_hllayersizes),__FUNCTION__+": incorrect (nonexistent) K1"))
return(EMPTY_VALUE);
//--- check
if(!CAp::Assert(i1>=0 && i1<network.m_hllayersizes[k1],__FUNCTION__+": incorrect (nonexistent) I1"))
return(EMPTY_VALUE);
//--- search
network.m_integerbuf[0]=k0;
network.m_integerbuf[1]=i0;
network.m_integerbuf[2]=k1;
network.m_integerbuf[3]=i1;
//--- function call
highlevelidx=CApServ::RecSearch(network.m_hlconnections,m_hlconm_nfieldwidth,4,0,ccnt,network.m_integerbuf);
//--- check
if(highlevelidx>=0)
result=network.m_weights[network.m_hlconnections[highlevelidx*m_hlconm_nfieldwidth+4]];
else
result=0;
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| This function sets offset/scaling coefficients for I-th input of |
//| the network. |
//| INPUT PARAMETERS: |
//| Network - network |
//| I - input index |
//| Mean - mean term |
//| Sigma - sigma term (if zero,will be replaced by 1.0) |
//| NTE: I-th input is passed through linear transformation |
//| IN[i]=(IN[i]-Mean)/Sigma |
//| before feeding to the network. This function sets Mean and Sigma.|
//+------------------------------------------------------------------+
static void CMLPBase::MLPSetInputScaling(CMultilayerPerceptron &network,
const int i,const double mean,
double sigma)
{
//--- check
if(!CAp::Assert(i>=0 && i<network.m_hllayersizes[0],__FUNCTION__+": incorrect (nonexistent) I"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(mean),__FUNCTION__+": infinite or NAN Mean"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(sigma),__FUNCTION__+": infinite or NAN Sigma"))
return;
//--- check
if(sigma==0.0)
sigma=1;
//--- change values
network.m_columnmeans[i]=mean;
network.m_columnsigmas[i]=sigma;
}
//+------------------------------------------------------------------+
//| This function sets offset/scaling coefficients for I-th output of|
//| the network. |
//| INPUT PARAMETERS: |
//| Network - network |
//| I - input index |
//| Mean - mean term |
//| Sigma - sigma term (if zero, will be replaced by 1.0)|
//| OUTPUT PARAMETERS: |
//| NOTE: I-th output is passed through linear transformation |
//| OUT[i] = OUT[i]*Sigma+Mean |
//| before returning it to user. This function sets Sigma/Mean. In |
//| case we have SOFTMAX-normalized network, you can not set (Sigma, |
//| Mean) to anything other than(0.0,1.0) - this function will throw |
//| exception. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPSetOutputScaling(CMultilayerPerceptron &network,
const int i,const double mean,
double sigma)
{
//--- check
if(!CAp::Assert(i>=0 && i<network.m_hllayersizes[CAp::Len(network.m_hllayersizes)-1],__FUNCTION__+": incorrect (nonexistent) I"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(mean),__FUNCTION__+": infinite or NAN Mean"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(sigma),__FUNCTION__+": infinite or NAN Sigma"))
return;
//--- check
if(network.m_structinfo[6]==1)
{
//--- check
if(!CAp::Assert(mean==0.0,__FUNCTION__+": you can not set non-zero Mean term for classifier network"))
return;
//--- check
if(!CAp::Assert(sigma==1.0,__FUNCTION__+": you can not set non-unit Sigma term for classifier network"))
return;
}
else
{
//--- check
if(sigma==0.0)
sigma=1;
//--- change values
network.m_columnmeans[network.m_hllayersizes[0]+i]=mean;
network.m_columnsigmas[network.m_hllayersizes[0]+i]=sigma;
}
}
//+------------------------------------------------------------------+
//| This function modifies information about Ith neuron of Kth layer |
//| INPUT PARAMETERS: |
//| Network - network |
//| K - layer index |
//| I - neuron index (within layer) |
//| FKind - activation function type (used by |
//| MLPActivationFunction()) this value must be |
//| zero for input neurons (you can not set |
//| activation function for input neurons) |
//| Threshold - also called offset, bias |
//| this value must be zero for input neurons |
//| (you can not set threshold for input neurons)|
//| NOTES: |
//| 1. this function throws exception if layer or neuron with given |
//| index do not exists. |
//| 2. this function also throws exception when you try to set |
//| non-linear activation function for input neurons (any kind |
//| of network) or for output neurons of classifier network. |
//| 3. this function throws exception when you try to set non-zero |
//| threshold for input neurons (any kind of network). |
//+------------------------------------------------------------------+
static void CMLPBase::MLPSetNeuronInfo(CMultilayerPerceptron &network,
const int k,const int i,
const int fkind,const double threshold)
{
//--- create variables
int ncnt=0;
int istart=0;
int highlevelidx=0;
int activationoffset=0;
//--- check
if(!CAp::Assert(CMath::IsFinite(threshold),__FUNCTION__+": infinite or NAN Threshold"))
return;
//--- convenience vars
ncnt=CAp::Len(network.m_hlneurons)/m_hlm_nfieldwidth;
istart=network.m_structinfo[5];
//--- search
network.m_integerbuf[0]=k;
network.m_integerbuf[1]=i;
//--- function call
highlevelidx=CApServ::RecSearch(network.m_hlneurons,m_hlm_nfieldwidth,2,0,ncnt,network.m_integerbuf);
//--- check
if(!CAp::Assert(highlevelidx>=0,__FUNCTION__+": incorrect (nonexistent) layer or neuron index"))
return;
//--- activation function
if(network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+2]>=0)
{
activationoffset=istart+network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+2]*m_nfieldwidth;
network.m_structinfo[activationoffset+0]=fkind;
}
else
{
//--- check
if(!CAp::Assert(fkind==0,__FUNCTION__+": you try to set activation function for neuron which can not have one"))
return;
}
//--- Threshold
if(network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+3]>=0)
network.m_weights[network.m_hlneurons[highlevelidx*m_hlm_nfieldwidth+3]]=threshold;
else
{
//--- check
if(!CAp::Assert(threshold==0.0,__FUNCTION__+": you try to set non-zero threshold for neuron which can not have one"))
return;
}
}
//+------------------------------------------------------------------+
//| This function modifies information about connection from I0-th |
//| neuron of K0-th layer to I1-th neuron of K1-th layer. |
//| INPUT PARAMETERS: |
//| Network - network |
//| K0 - layer index |
//| I0 - neuron index (within layer) |
//| K1 - layer index |
//| I1 - neuron index (within layer) |
//| W - connection weight (must be zero for |
//| non-existent connections) |
//| This function: |
//| 1. throws exception if layer or neuron with given index do not |
//| exists. |
//| 2. throws exception if you try to set non-zero weight for |
//| non-existent connection |
//+------------------------------------------------------------------+
static void CMLPBase::MLPSetWeight(CMultilayerPerceptron &network,const int k0,
const int i0,const int k1,
const int i1,const double w)
{
//--- create variables
int ccnt=0;
int highlevelidx=0;
//--- initialization
ccnt=CAp::Len(network.m_hlconnections)/m_hlconm_nfieldwidth;
//--- check params
if(!CAp::Assert(k0>=0 && k0<CAp::Len(network.m_hllayersizes),__FUNCTION__+": incorrect (nonexistent) K0"))
return;
//--- check
if(!CAp::Assert(i0>=0 && i0<network.m_hllayersizes[k0],__FUNCTION__+": incorrect (nonexistent) I0"))
return;
//--- check
if(!CAp::Assert(k1>=0 && k1<CAp::Len(network.m_hllayersizes),__FUNCTION__+": incorrect (nonexistent) K1"))
return;
//--- check
if(!CAp::Assert(i1>=0 && i1<network.m_hllayersizes[k1],__FUNCTION__+": incorrect (nonexistent) I1"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(w),__FUNCTION__+": infinite or NAN weight"))
return;
//--- search
network.m_integerbuf[0]=k0;
network.m_integerbuf[1]=i0;
network.m_integerbuf[2]=k1;
network.m_integerbuf[3]=i1;
//--- function call
highlevelidx=CApServ::RecSearch(network.m_hlconnections,m_hlconm_nfieldwidth,4,0,ccnt,network.m_integerbuf);
//--- check
if(highlevelidx>=0)
network.m_weights[network.m_hlconnections[highlevelidx*m_hlconm_nfieldwidth+4]]=w;
else
{
//--- check
if(!CAp::Assert(w==0.0,__FUNCTION__+": you try to set non-zero weight for non-existent connection"))
return;
}
}
//+------------------------------------------------------------------+
//| Neural network activation function |
//| INPUT PARAMETERS: |
//| NET - neuron input |
//| K - function index (zero for linear function) |
//| OUTPUT PARAMETERS: |
//| F - function |
//| DF - its derivative |
//| D2F - its second derivative |
//+------------------------------------------------------------------+
static void CMLPBase::MLPActivationFunction(double net,const int k,double &f,
double &df,double &d2f)
{
//--- create variables
double net2=0;
double arg=0;
double root=0;
double r=0;
//--- initialization
f=0;
df=0;
d2f=0;
//--- check
if(k==0 || k==-5)
{
f=net;
df=1;
d2f=0;
//--- exit the function
return;
}
//--- check
if(k==1)
{
//--- TanH activation function
if(MathAbs(net)<100.0)
f=MathTanh(net);
else
f=MathSign(net);
//--- change values
df=1-CMath::Sqr(f);
d2f=-(2*f*df);
//--- exit the function
return;
}
//--- check
if(k==3)
{
//--- EX activation function
if(net>=0.0)
{
//--- change values
net2=net*net;
arg=net2+1;
root=MathSqrt(arg);
f=net+root;
r=net/root;
df=1+r;
d2f=(root-net*r)/arg;
}
else
{
//--- change values
f=MathExp(net);
df=f;
d2f=f;
}
//--- exit the function
return;
}
//--- check
if(k==2)
{
//--- calculation
f=MathExp(-CMath::Sqr(net));
df=-(2*net*f);
d2f=-(2*(f+df*net));
//--- exit the function
return;
}
//--- change values
f=0;
df=0;
d2f=0;
}
//+------------------------------------------------------------------+
//| Procesing |
//| INPUT PARAMETERS: |
//| Network - neural network |
//| X - input vector, array[0..NIn-1]. |
//| OUTPUT PARAMETERS: |
//| Y - result. Regression estimate when solving |
//| regression task, vector of posterior |
//| probabilities for classification task. |
//| See also MLPProcessI |
//+------------------------------------------------------------------+
static void CMLPBase::MLPProcess(CMultilayerPerceptron &network,double &x[],
double &y[])
{
//--- check
if(CAp::Len(y)<network.m_structinfo[2])
ArrayResizeAL(y,network.m_structinfo[2]);
//--- function call
MLPInternalProcessVector(network.m_structinfo,network.m_weights,network.m_columnmeans,network.m_columnsigmas,network.m_neurons,network.m_dfdnet,x,y);
}
//+------------------------------------------------------------------+
//| 'interactive' variant of MLPProcess for languages like Python |
//| which support constructs like "Y = MLPProcess(NN,X)" and |
//| interactive mode of the interpreter |
//| This function allocates new array on each call, so it is |
//| significantly slower than its 'non-interactive' counterpart, |
//| but it is more convenient when you call it from command line. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPProcessI(CMultilayerPerceptron &network,double &x[],
double &y[])
{
//--- function call
MLPProcess(network,x,y);
}
//+------------------------------------------------------------------+
//| Error function for neural network,internal subroutine. |
//+------------------------------------------------------------------+
static double CMLPBase::MLPError(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int ssize)
{
//--- create variables
double result=0;
int i=0;
int k=0;
int nin=0;
int nout=0;
int wcount=0;
double e=0;
int i_=0;
int i1_=0;
//--- function call
MLPProperties(network,nin,nout,wcount);
//--- calculation
for(i=0;i<=ssize-1;i++)
{
for(i_=0;i_<=nin-1;i_++)
network.m_x[i_]=xy[i][i_];
//--- function call
MLPProcess(network,network.m_x,network.m_y);
//--- check
if(MLPIsSoftMax(network))
{
//--- class labels outputs
k=(int)MathRound(xy[i][nin]);
//--- check
if(k>=0 && k<nout)
network.m_y[k]=network.m_y[k]-1;
}
else
{
//--- real outputs
i1_=nin;
for(i_=0;i_<=nout-1;i_++)
network.m_y[i_]=network.m_y[i_]-xy[i][i_+i1_];
}
//--- calculation
e=0.0;
for(i_=0;i_<=nout-1;i_++)
e+=network.m_y[i_]*network.m_y[i_];
result=result+e/2;
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Natural error function for neural network,internal subroutine. |
//+------------------------------------------------------------------+
static double CMLPBase::MLPErrorN(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int ssize)
{
//--- create variables
double result=0;
int i=0;
int k=0;
int nin=0;
int nout=0;
int wcount=0;
double e=0;
int i_=0;
int i1_=0;
//--- function call
MLPProperties(network,nin,nout,wcount);
//--- calculation
for(i=0;i<=ssize-1;i++)
{
//--- Process vector
for(i_=0;i_<=nin-1;i_++)
network.m_x[i_]=xy[i][i_];
//--- function call
MLPProcess(network,network.m_x,network.m_y);
//--- Update error function
if(network.m_structinfo[6]==0)
{
//--- Least squares error function
i1_=nin;
for(i_=0;i_<=nout-1;i_++)
network.m_y[i_]=network.m_y[i_]-xy[i][i_+i1_];
//--- calculation
e=0.0;
for(i_=0;i_<=nout-1;i_++)
e+=network.m_y[i_]*network.m_y[i_];
result=result+e/2;
}
else
{
//--- Cross-entropy error function
k=(int)MathRound(xy[i][nin]);
//--- check
if(k>=0 && k<nout)
result=result+SafeCrossEntropy(1,network.m_y[k]);
}
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Classification error |
//+------------------------------------------------------------------+
static int CMLPBase::MLPClsError(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int ssize)
{
//--- create variables
int result=0;
int i=0;
int j=0;
int nin=0;
int nout=0;
int wcount=0;
int nn=0;
int ns=0;
int nmax=0;
int i_=0;
//--- creating arrays
double workx[];
double worky[];
//--- function call
MLPProperties(network,nin,nout,wcount);
//--- allocation
ArrayResizeAL(workx,nin);
ArrayResizeAL(worky,nout);
//--- calculation
for(i=0;i<=ssize-1;i++)
{
//--- Process
for(i_=0;i_<=nin-1;i_++)
workx[i_]=xy[i][i_];
//--- function call
MLPProcess(network,workx,worky);
//--- Network version of the answer
nmax=0;
for(j=0;j<=nout-1;j++)
{
//--- check
if(worky[j]>worky[nmax])
nmax=j;
}
nn=nmax;
//--- Right answer
if(MLPIsSoftMax(network))
ns=(int)MathRound(xy[i][nin]);
else
{
nmax=0;
for(j=0;j<=nout-1;j++)
{
//--- check
if(xy[i][nin+j]>xy[i][nin+nmax])
nmax=j;
}
ns=nmax;
}
//--- compare
if(nn!=ns)
result=result+1;
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Relative classification error on the test set |
//| INPUT PARAMETERS: |
//| Network - network |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| percent of incorrectly classified cases. Works both for |
//| classifier networks and general purpose networks used as |
//| classifiers. |
//+------------------------------------------------------------------+
static double CMLPBase::MLPRelClsError(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int npoints)
{
//--- return result
return((double)MLPClsError(network,xy,npoints)/(double)npoints);
}
//+------------------------------------------------------------------+
//| Average cross-entropy (in bits per element) on the test set |
//| INPUT PARAMETERS: |
//| Network - neural network |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| CrossEntropy/(NPoints*LN(2)). |
//| Zero if network solves regression task. |
//+------------------------------------------------------------------+
static double CMLPBase::MLPAvgCE(CMultilayerPerceptron &network,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double result=0;
int nin=0;
int nout=0;
int wcount=0;
//--- check
if(MLPIsSoftMax(network))
{
//--- function call
MLPProperties(network,nin,nout,wcount);
//--- get result
result=MLPErrorN(network,xy,npoints)/(npoints*MathLog(2));
}
else
result=0;
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| RMS error on the test set |
//| INPUT PARAMETERS: |
//| Network - neural network |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| root mean square error. |
//| Its meaning for regression task is obvious. As for |
//| classification task,RMS error means error when estimating |
//| posterior probabilities. |
//+------------------------------------------------------------------+
static double CMLPBase::MLPRMSError(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int npoints)
{
//--- create variables
int nin=0;
int nout=0;
int wcount=0;
//--- function call
MLPProperties(network,nin,nout,wcount);
//--- return result
return(MathSqrt(2*MLPError(network,xy,npoints)/(npoints*nout)));
}
//+------------------------------------------------------------------+
//| Average error on the test set |
//| INPUT PARAMETERS: |
//| Network - neural network |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| Its meaning for regression task is obvious. As for |
//| classification task,it means average error when estimating |
//| posterior probabilities. |
//+------------------------------------------------------------------+
static double CMLPBase::MLPAvgError(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int npoints)
{
//--- create variables
double result=0;
int i=0;
int j=0;
int k=0;
int nin=0;
int nout=0;
int wcount=0;
int i_=0;
//--- function call
MLPProperties(network,nin,nout,wcount);
//--- calculation
for(i=0;i<=npoints-1;i++)
{
for(i_=0;i_<=nin-1;i_++)
network.m_x[i_]=xy[i][i_];
//--- function call
MLPProcess(network,network.m_x,network.m_y);
//--- check
if(MLPIsSoftMax(network))
{
//--- class labels
k=(int)MathRound(xy[i][nin]);
for(j=0;j<=nout-1;j++)
{
//--- check
if(j==k)
result=result+MathAbs(1-network.m_y[j]);
else
result=result+MathAbs(network.m_y[j]);
}
}
else
{
//--- real outputs
for(j=0;j<=nout-1;j++)
result=result+MathAbs(xy[i][nin+j]-network.m_y[j]);
}
}
//--- return result
return(result/(npoints*nout));
}
//+------------------------------------------------------------------+
//| Average relative error on the test set |
//| INPUT PARAMETERS: |
//| Network - neural network |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| Its meaning for regression task is obvious. As for |
//| classification task, it means average relative error when |
//| estimating posterior probability of belonging to the correct |
//| class. |
//+------------------------------------------------------------------+
static double CMLPBase::MLPAvgRelError(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int npoints)
{
//--- create variables
double result=0;
int i=0;
int j=0;
int k=0;
int lk=0;
int nin=0;
int nout=0;
int wcount=0;
int i_=0;
//--- function call
MLPProperties(network,nin,nout,wcount);
//--- initialization
result=0;
k=0;
//--- calculation
for(i=0;i<=npoints-1;i++)
{
for(i_=0;i_<=nin-1;i_++)
network.m_x[i_]=xy[i][i_];
//--- function call
MLPProcess(network,network.m_x,network.m_y);
//--- check
if(MLPIsSoftMax(network))
{
//--- class labels
lk=(int)MathRound(xy[i][nin]);
for(j=0;j<=nout-1;j++)
{
//--- check
if(j==lk)
{
result=result+MathAbs(1-network.m_y[j]);
k=k+1;
}
}
}
else
{
//--- real outputs
for(j=0;j<=nout-1;j++)
{
//--- check
if(xy[i][nin+j]!=0.0)
{
result=result+MathAbs(xy[i][nin+j]-network.m_y[j])/MathAbs(xy[i][nin+j]);
k=k+1;
}
}
}
}
//--- check
if(k!=0)
result=result/k;
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Gradient calculation |
//| INPUT PARAMETERS: |
//| Network - network initialized with one of the network |
//| creation funcs |
//| X - input vector, length of array must be at least |
//| NIn |
//| DesiredY- desired outputs, length of array must be at least|
//| NOut |
//| Grad - possibly preallocated array. If size of array is |
//| smaller than WCount, it will be reallocated. It |
//| is recommended to reuse previously allocated |
//| array to reduce allocation overhead. |
//| OUTPUT PARAMETERS: |
//| E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) |
//| Grad - gradient of E with respect to weights of network,|
//| array[WCount] |
//+------------------------------------------------------------------+
static void CMLPBase::MLPGrad(CMultilayerPerceptron &network,double &x[],
double &desiredy[],double &e,double &grad[])
{
//--- create variables
int i=0;
int nout=0;
int ntotal=0;
//--- Alloc
if(CAp::Len(grad)<network.m_structinfo[4])
ArrayResizeAL(grad,network.m_structinfo[4]);
//--- Prepare dError/dOut,internal structures
MLPProcess(network,x,network.m_y);
//--- initialization
nout=network.m_structinfo[2];
ntotal=network.m_structinfo[3];
e=0;
//--- change values
for(i=0;i<=ntotal-1;i++)
network.m_derror[i]=0;
for(i=0;i<=nout-1;i++)
{
network.m_derror[ntotal-nout+i]=network.m_y[i]-desiredy[i];
e=e+CMath::Sqr(network.m_y[i]-desiredy[i])/2;
}
//--- gradient
MLPInternalCalculateGradient(network,network.m_neurons,network.m_weights,network.m_derror,grad,false);
}
//+------------------------------------------------------------------+
//| Gradient calculation (natural error function is used) |
//| INPUT PARAMETERS: |
//| Network - network initialized with one of the network |
//| creation funcs |
//| X - input vector, length of array must be at least |
//| NIn |
//| DesiredY- desired outputs, length of array must be at least|
//| NOut |
//| Grad - possibly preallocated array. If size of array is |
//| smaller than WCount, it will be reallocated. It |
//| is recommended to reuse previously allocated |
//| array to reduce allocation overhead. |
//| OUTPUT PARAMETERS: |
//| E - error function, sum-of-squares for regression |
//| networks, cross-entropy for classification |
//| networks. |
//| Grad - gradient of E with respect to weights of network,|
//| array[WCount] |
//+------------------------------------------------------------------+
static void CMLPBase::MLPGradN(CMultilayerPerceptron &network,double &x[],
double &desiredy[],double &e,double &grad[])
{
//--- create variables
double s=0;
int i=0;
int nout=0;
int ntotal=0;
//--- initialization
e=0;
//--- Alloc
if(CAp::Len(grad)<network.m_structinfo[4])
ArrayResizeAL(grad,network.m_structinfo[4]);
//--- Prepare dError/dOut,internal structures
MLPProcess(network,x,network.m_y);
//--- change values
nout=network.m_structinfo[2];
ntotal=network.m_structinfo[3];
for(i=0;i<=ntotal-1;i++)
network.m_derror[i]=0;
e=0;
//--- check
if(network.m_structinfo[6]==0)
{
//--- Regression network,least squares
for(i=0;i<=nout-1;i++)
{
network.m_derror[ntotal-nout+i]=network.m_y[i]-desiredy[i];
e=e+CMath::Sqr(network.m_y[i]-desiredy[i])/2;
}
}
else
{
//--- Classification network,cross-entropy
s=0;
for(i=0;i<=nout-1;i++)
s=s+desiredy[i];
for(i=0;i<=nout-1;i++)
{
network.m_derror[ntotal-nout+i]=s*network.m_y[i]-desiredy[i];
e=e+SafeCrossEntropy(desiredy[i],network.m_y[i]);
}
}
//--- gradient
MLPInternalCalculateGradient(network,network.m_neurons,network.m_weights,network.m_derror,grad,true);
}
//+------------------------------------------------------------------+
//| Batch gradient calculation for a set of inputs/outputs |
//| INPUT PARAMETERS: |
//| Network - network initialized with one of the network |
//| creation funcs |
//| XY - set of inputs/outputs; one sample = one row; |
//| first NIn columns contain inputs, |
//| next NOut columns - desired outputs. |
//| SSize - number of elements in XY |
//| Grad - possibly preallocated array. If size of array is |
//| smaller than WCount, it will be reallocated. It |
//| is recommended to reuse previously allocated |
//| array to reduce allocation overhead. |
//| OUTPUT PARAMETERS: |
//| E - error function, SUM(sqr(y[i]-desiredy[i])/2,i) |
//| Grad - gradient of E with respect to weights of network,|
//| array[WCount] |
//+------------------------------------------------------------------+
static void CMLPBase::MLPGradBatch(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int ssize,
double &e,double &grad[])
{
//--- create variables
int i=0;
int nin=0;
int nout=0;
int wcount=0;
//--- function call
MLPProperties(network,nin,nout,wcount);
//--- initialization
for(i=0;i<=wcount-1;i++)
grad[i]=0;
e=0;
i=0;
//--- calculation
while(i<=ssize-1)
{
MLPChunkedGradient(network,xy,i,MathMin(ssize,i+m_chunksize)-i,e,grad,false);
i=i+m_chunksize;
}
}
//+------------------------------------------------------------------+
//| Batch gradient calculation for a set of inputs/outputs |
//| (natural error function is used) |
//| INPUT PARAMETERS: |
//| Network - network initialized with one of the network |
//| creation funcs |
//| XY - set of inputs/outputs; one sample=one row; |
//| first NIn columns contain inputs, |
//| next NOut columns - desired outputs. |
//| SSize - number of elements in XY |
//| Grad - possibly preallocated array. If size of array is |
//| smaller than WCount, it will be reallocated. It |
//| is recommended to reuse previously allocated |
//| array to reduce allocation overhead. |
//| OUTPUT PARAMETERS: |
//| E - error function, sum-of-squares for regression |
//| networks, cross-entropy for classification |
//| networks. |
//| Grad - gradient of E with respect to weights of network,|
//| array[WCount] |
//+------------------------------------------------------------------+
static void CMLPBase::MLPGradNBatch(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int ssize,
double &e,double &grad[])
{
//--- create variables
int i=0;
int nin=0;
int nout=0;
int wcount=0;
//--- function call
MLPProperties(network,nin,nout,wcount);
//--- initialization
for(i=0;i<=wcount-1;i++)
grad[i]=0;
e=0;
i=0;
//--- calculation
while(i<=ssize-1)
{
MLPChunkedGradient(network,xy,i,MathMin(ssize,i+m_chunksize)-i,e,grad,true);
i=i+m_chunksize;
}
}
//+------------------------------------------------------------------+
//| Batch Hessian calculation (natural error function) using |
//| R-algorithm. Internal subroutine. |
//| Hessian calculation based on R-algorithm described in |
//| "Fast Exact Multiplication by the Hessian", |
//| B. A. Pearlmutter, |
//| Neural Computation, 1994. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPHessianNBatch(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int ssize,
double &e,double &grad[],
CMatrixDouble &h)
{
//--- initialization
e=0;
//--- function call
MLPHessianBatchInternal(network,xy,ssize,true,e,grad,h);
}
//+------------------------------------------------------------------+
//| Batch Hessian calculation using R-algorithm. |
//| Internal subroutine. |
//| Hessian calculation based on R-algorithm described in |
//| "Fast Exact Multiplication by the Hessian", |
//| B. A. Pearlmutter, |
//| Neural Computation, 1994. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPHessianBatch(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int ssize,
double &e,double &grad[],
CMatrixDouble &h)
{
//--- initialization
e=0;
//--- function call
MLPHessianBatchInternal(network,xy,ssize,false,e,grad,h);
}
//+------------------------------------------------------------------+
//| Internal subroutine, shouldn't be called by user. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPInternalProcessVector(int &structinfo[],double &weights[],
double &columnmeans[],
double &columnsigmas[],
double &neurons[],
double &dfdnet[],
double &x[],double &y[])
{
//--- create variables
int i=0;
int n1=0;
int n2=0;
int w1=0;
int w2=0;
int ntotal=0;
int nin=0;
int nout=0;
int istart=0;
int offs=0;
double net=0;
double f=0;
double df=0;
double d2f=0;
double mx=0;
bool perr;
int i_=0;
int i1_=0;
//--- Read network geometry
nin=structinfo[1];
nout=structinfo[2];
ntotal=structinfo[3];
istart=structinfo[5];
//--- Inputs standartisation and putting in the network
for(i=0;i<=nin-1;i++)
{
//--- check
if(columnsigmas[i]!=0.0)
neurons[i]=(x[i]-columnmeans[i])/columnsigmas[i];
else
neurons[i]=x[i]-columnmeans[i];
}
//--- Process network
for(i=0;i<=ntotal-1;i++)
{
offs=istart+i*m_nfieldwidth;
//--- check
if(structinfo[offs+0]>0 || structinfo[offs+0]==-5)
{
//--- Activation function
MLPActivationFunction(neurons[structinfo[offs+2]],structinfo[offs+0],f,df,d2f);
//--- change values
neurons[i]=f;
dfdnet[i]=df;
continue;
}
//--- check
if(structinfo[offs+0]==0)
{
//--- Adaptive summator
n1=structinfo[offs+2];
n2=n1+structinfo[offs+1]-1;
w1=structinfo[offs+3];
w2=w1+structinfo[offs+1]-1;
i1_=(n1)-(w1);
net=0.0;
//--- calculation
for(i_=w1;i_<=w2;i_++)
net+=weights[i_]*neurons[i_+i1_];
neurons[i]=net;
dfdnet[i]=1.0;
continue;
}
//--- check
if(structinfo[offs+0]<0)
{
perr=true;
//--- check
if(structinfo[offs+0]==-2)
{
//--- input neuron,left unchanged
perr=false;
}
//--- check
if(structinfo[offs+0]==-3)
{
//--- "-1" neuron
neurons[i]=-1;
perr=false;
}
//--- check
if(structinfo[offs+0]==-4)
{
//--- "0" neuron
neurons[i]=0;
perr=false;
}
//--- check
if(!CAp::Assert(!perr,__FUNCTION__+": internal error - unknown neuron type!"))
return;
continue;
}
}
//--- Extract result
i1_=ntotal-nout;
for(i_=0;i_<=nout-1;i_++)
y[i_]=neurons[i_+i1_];
//--- Softmax post-processing or standardisation if needed
if(!CAp::Assert(structinfo[6]==0 || structinfo[6]==1,__FUNCTION__+": unknown normalization type!"))
return;
//--- check
if(structinfo[6]==1)
{
//--- Softmax
mx=y[0];
for(i=1;i<=nout-1;i++)
mx=MathMax(mx,y[i]);
//--- calculation
net=0;
for(i=0;i<=nout-1;i++)
{
y[i]=MathExp(y[i]-mx);
net=net+y[i];
}
for(i=0;i<=nout-1;i++)
y[i]=y[i]/net;
}
else
{
//--- Standardisation
for(i=0;i<=nout-1;i++)
y[i]=y[i]*columnsigmas[nin+i]+columnmeans[nin+i];
}
}
//+------------------------------------------------------------------+
//| Serializer: allocation |
//+------------------------------------------------------------------+
static void CMLPBase::MLPAlloc(CSerializer &s,CMultilayerPerceptron &network)
{
//--- create variables
int i=0;
int j=0;
int k=0;
int fkind=0;
double threshold=0;
double v0=0;
double v1=0;
int nin=0;
int nout=0;
//--- initialization
nin=network.m_hllayersizes[0];
nout=network.m_hllayersizes[CAp::Len(network.m_hllayersizes)-1];
//--- preparation to serialize
s.Alloc_Entry();
s.Alloc_Entry();
s.Alloc_Entry();
//--- function call
CApServ::AllocIntegerArray(s,network.m_hllayersizes,-1);
for(i=1;i<=CAp::Len(network.m_hllayersizes)-1;i++)
{
for(j=0;j<=network.m_hllayersizes[i]-1;j++)
{
//--- function call
MLPGetNeuronInfo(network,i,j,fkind,threshold);
//--- preparation to serialize
s.Alloc_Entry();
s.Alloc_Entry();
for(k=0;k<=network.m_hllayersizes[i-1]-1;k++)
s.Alloc_Entry();
}
}
for(j=0;j<=nin-1;j++)
{
//--- function call
MLPGetInputScaling(network,j,v0,v1);
//--- preparation to serialize
s.Alloc_Entry();
s.Alloc_Entry();
}
for(j=0;j<=nout-1;j++)
{
//--- function call
MLPGetOutputScaling(network,j,v0,v1);
//--- preparation to serialize
s.Alloc_Entry();
s.Alloc_Entry();
}
}
//+------------------------------------------------------------------+
//| Serializer: serialization |
//+------------------------------------------------------------------+
static void CMLPBase::MLPSerialize(CSerializer &s,CMultilayerPerceptron &network)
{
//--- create variables
int i=0;
int j=0;
int k=0;
int fkind=0;
double threshold=0;
double v0=0;
double v1=0;
int nin=0;
int nout=0;
//--- change values
nin=network.m_hllayersizes[0];
nout=network.m_hllayersizes[CAp::Len(network.m_hllayersizes)-1];
//--- serializetion
s.Serialize_Int(CSCodes::GetMLPSerializationCode());
s.Serialize_Int(m_mlpfirstversion);
s.Serialize_Bool(MLPIsSoftMax(network));
//--- function call
CApServ::SerializeIntegerArray(s,network.m_hllayersizes,-1);
for(i=1;i<=CAp::Len(network.m_hllayersizes)-1;i++)
{
for(j=0;j<=network.m_hllayersizes[i]-1;j++)
{
//--- function call
MLPGetNeuronInfo(network,i,j,fkind,threshold);
//--- serializetion
s.Serialize_Int(fkind);
s.Serialize_Double(threshold);
for(k=0;k<=network.m_hllayersizes[i-1]-1;k++)
s.Serialize_Double(MLPGetWeight(network,i-1,k,i,j));
}
}
for(j=0;j<=nin-1;j++)
{
//--- function call
MLPGetInputScaling(network,j,v0,v1);
//--- serializetion
s.Serialize_Double(v0);
s.Serialize_Double(v1);
}
for(j=0;j<=nout-1;j++)
{
//--- function call
MLPGetOutputScaling(network,j,v0,v1);
//--- serializetion
s.Serialize_Double(v0);
s.Serialize_Double(v1);
}
}
//+------------------------------------------------------------------+
//| Serializer: unserialization |
//+------------------------------------------------------------------+
static void CMLPBase::MLPUnserialize(CSerializer &s,CMultilayerPerceptron &network)
{
//--- create variables
int i0=0;
int i1=0;
int i=0;
int j=0;
int k=0;
int fkind=0;
double threshold=0;
double v0=0;
double v1=0;
int nin=0;
int nout=0;
bool issoftmax;
//--- create array
int layersizes[];
//--- check correctness of header
i0=s.Unserialize_Int();
//--- check
if(!CAp::Assert(i0==CSCodes::GetMLPSerializationCode(),__FUNCTION__+": stream header corrupted"))
return;
//--- unserializetion
i1=s.Unserialize_Int();
//--- check
if(!CAp::Assert(i1==m_mlpfirstversion,__FUNCTION__+": stream header corrupted"))
return;
//--- Create network
issoftmax=s.Unserialize_Bool();
//--- function call
CApServ::UnserializeIntegerArray(s,layersizes);
//--- check
if(!CAp::Assert((CAp::Len(layersizes)==2 || CAp::Len(layersizes)==3) || CAp::Len(layersizes)==4,__FUNCTION__+": too many hidden layers!"))
return;
//--- change values
nin=layersizes[0];
nout=layersizes[CAp::Len(layersizes)-1];
//--- check
if(CAp::Len(layersizes)==2)
{
//--- check
if(issoftmax)
MLPCreateC0(layersizes[0],layersizes[1],network);
else
MLPCreate0(layersizes[0],layersizes[1],network);
}
//--- check
if(CAp::Len(layersizes)==3)
{
//--- check
if(issoftmax)
MLPCreateC1(layersizes[0],layersizes[1],layersizes[2],network);
else
MLPCreate1(layersizes[0],layersizes[1],layersizes[2],network);
}
//--- check
if(CAp::Len(layersizes)==4)
{
//--- check
if(issoftmax)
MLPCreateC2(layersizes[0],layersizes[1],layersizes[2],layersizes[3],network);
else
MLPCreate2(layersizes[0],layersizes[1],layersizes[2],layersizes[3],network);
}
//--- Load neurons and weights
for(i=1;i<=CAp::Len(layersizes)-1;i++)
{
for(j=0;j<=layersizes[i]-1;j++)
{
//--- unserializetion
fkind=s.Unserialize_Int();
threshold=s.Unserialize_Double();
//--- function call
MLPSetNeuronInfo(network,i,j,fkind,threshold);
//--- unserializetion
for(k=0;k<=layersizes[i-1]-1;k++)
{
v0=s.Unserialize_Double();
//--- function call
MLPSetWeight(network,i-1,k,i,j,v0);
}
}
}
//
//--- Load standartizator
//
for(j=0;j<=nin-1;j++)
{
//--- unserializetion
v0=s.Unserialize_Double();
v1=s.Unserialize_Double();
//--- function call
MLPSetInputScaling(network,j,v0,v1);
}
for(j=0;j<=nout-1;j++)
{
//--- unserializetion
v0=s.Unserialize_Double();
v1=s.Unserialize_Double();
//--- function call
MLPSetOutputScaling(network,j,v0,v1);
}
}
//+------------------------------------------------------------------+
//| Internal subroutine: adding new input layer to network |
//+------------------------------------------------------------------+
static void CMLPBase::AddInputLayer(const int ncount,int &lsizes[],
int &ltypes[],int &lconnfirst[],
int &lconnlast[],int &lastproc)
{
//--- change values
lsizes[0]=ncount;
ltypes[0]=-2;
lconnfirst[0]=0;
lconnlast[0]=0;
lastproc=0;
}
//+------------------------------------------------------------------+
//| Internal subroutine: adding new summator layer to network |
//+------------------------------------------------------------------+
static void CMLPBase::AddBiasedSummatorLayer(const int ncount,int &lsizes[],
int &ltypes[],int &lconnfirst[],
int &lconnlast[],int &lastproc)
{
//--- change values
lsizes[lastproc+1]=1;
ltypes[lastproc+1]=-3;
lconnfirst[lastproc+1]=0;
lconnlast[lastproc+1]=0;
lsizes[lastproc+2]=ncount;
ltypes[lastproc+2]=0;
lconnfirst[lastproc+2]=lastproc;
lconnlast[lastproc+2]=lastproc+1;
lastproc=lastproc+2;
}
//+------------------------------------------------------------------+
//| Internal subroutine: adding new summator layer to network |
//+------------------------------------------------------------------+
static void CMLPBase::AddActivationLayer(const int functype,int &lsizes[],
int &ltypes[],int &lconnfirst[],
int &lconnlast[],int &lastproc)
{
//--- check
if(!CAp::Assert(functype>0 || functype==-5,__FUNCTION__+": incorrect function type"))
return;
//--- change values
lsizes[lastproc+1]=lsizes[lastproc];
ltypes[lastproc+1]=functype;
lconnfirst[lastproc+1]=lastproc;
lconnlast[lastproc+1]=lastproc;
lastproc=lastproc+1;
}
//+------------------------------------------------------------------+
//| Internal subroutine: adding new zero layer to network |
//+------------------------------------------------------------------+
static void CMLPBase::AddZeroLayer(int &lsizes[],int &ltypes[],
int &lconnfirst[],int &lconnlast[],
int &lastproc)
{
//--- change values
lsizes[lastproc+1]=1;
ltypes[lastproc+1]=-4;
lconnfirst[lastproc+1]=0;
lconnlast[lastproc+1]=0;
lastproc=lastproc+1;
}
//+------------------------------------------------------------------+
//| This routine adds input layer to the high-level description of |
//| the network. |
//| It modifies Network.HLConnections and Network.HLNeurons and |
//| assumes that these arrays have enough place to store data. |
//| It accepts following parameters: |
//| Network - network |
//| ConnIdx - index of the first free entry in the |
//| HLConnections |
//| NeuroIdx - index of the first free entry in the |
//| HLNeurons |
//| StructInfoIdx- index of the first entry in the low level |
//| description of the current layer (in the |
//| StructInfo array) |
//| NIn - number of inputs |
//| It modified Network and indices. |
//+------------------------------------------------------------------+
static void CMLPBase::HLAddInputLayer(CMultilayerPerceptron &network,
int &connidx,int &neuroidx,
int &structinfoidx,int nin)
{
//--- create variables
int i=0;
int offs=0;
//--- initialization
offs=m_hlm_nfieldwidth*neuroidx;
//--- change values
for(i=0;i<=nin-1;i++)
{
network.m_hlneurons[offs+0]=0;
network.m_hlneurons[offs+1]=i;
network.m_hlneurons[offs+2]=-1;
network.m_hlneurons[offs+3]=-1;
offs=offs+m_hlm_nfieldwidth;
}
//--- change values
neuroidx=neuroidx+nin;
structinfoidx=structinfoidx+nin;
}
//+------------------------------------------------------------------+
//| This routine adds output layer to the high-level description of |
//| the network. |
//| It modifies Network.HLConnections and Network. HLNeurons and |
//| assumes that these arrays have enough place to store data. It |
//| accepts following parameters: |
//| Network - network |
//| ConnIdx - index of the first free entry in the |
//| HLConnections |
//| NeuroIdx - index of the first free entry in the |
//| HLNeurons |
//| StructInfoIdx- index of the first entry in the low level |
//| description of the current layer (in the |
//| StructInfo array) |
//| WeightsIdx - index of the first entry in the Weights |
//| array which corresponds to the current layer |
//| K - current layer index |
//| NPrev - number of neurons in the previous layer |
//| NOut - number of outputs |
//| IsCls - is it classifier network? |
//| IsLinear - is it network with linear output? |
//| It modified Network and ConnIdx/NeuroIdx/StructInfoIdx/WeightsIdx|
//+------------------------------------------------------------------+
static void CMLPBase::HLAddOutputLayer(CMultilayerPerceptron &network,
int &connidx,int &neuroidx,
int &structinfoidx,int &weightsidx,
const int k,const int nprev,
const int nout,const bool iscls,
const bool islinearout)
{
//--- create variables
int i=0;
int j=0;
int neurooffs=0;
int connoffs=0;
//--- check
if(!CAp::Assert((iscls && islinearout) || !iscls,__FUNCTION__+": internal error"))
return;
//--- initialization
neurooffs=m_hlm_nfieldwidth*neuroidx;
connoffs=m_hlconm_nfieldwidth*connidx;
//--- check
if(!iscls)
{
//--- Regression network
for(i=0;i<=nout-1;i++)
{
//--- change values
network.m_hlneurons[neurooffs+0]=k;
network.m_hlneurons[neurooffs+1]=i;
network.m_hlneurons[neurooffs+2]=structinfoidx+1+nout+i;
network.m_hlneurons[neurooffs+3]=weightsidx+nprev+(nprev+1)*i;
neurooffs=neurooffs+m_hlm_nfieldwidth;
}
for(i=0;i<=nprev-1;i++)
{
for(j=0;j<=nout-1;j++)
{
//--- change values
network.m_hlconnections[connoffs+0]=k-1;
network.m_hlconnections[connoffs+1]=i;
network.m_hlconnections[connoffs+2]=k;
network.m_hlconnections[connoffs+3]=j;
network.m_hlconnections[connoffs+4]=weightsidx+i+j*(nprev+1);
connoffs=connoffs+m_hlconm_nfieldwidth;
}
}
//--- change values
connidx=connidx+nprev*nout;
neuroidx=neuroidx+nout;
structinfoidx=structinfoidx+2*nout+1;
weightsidx=weightsidx+nout*(nprev+1);
}
else
{
//--- Classification network
for(i=0;i<=nout-2;i++)
{
//--- change values
network.m_hlneurons[neurooffs+0]=k;
network.m_hlneurons[neurooffs+1]=i;
network.m_hlneurons[neurooffs+2]=-1;
network.m_hlneurons[neurooffs+3]=weightsidx+nprev+(nprev+1)*i;
neurooffs=neurooffs+m_hlm_nfieldwidth;
}
//--- change values
network.m_hlneurons[neurooffs+0]=k;
network.m_hlneurons[neurooffs+1]=i;
network.m_hlneurons[neurooffs+2]=-1;
network.m_hlneurons[neurooffs+3]=-1;
for(i=0;i<=nprev-1;i++)
{
for(j=0;j<=nout-2;j++)
{
//--- change values
network.m_hlconnections[connoffs+0]=k-1;
network.m_hlconnections[connoffs+1]=i;
network.m_hlconnections[connoffs+2]=k;
network.m_hlconnections[connoffs+3]=j;
network.m_hlconnections[connoffs+4]=weightsidx+i+j*(nprev+1);
connoffs=connoffs+m_hlconm_nfieldwidth;
}
}
//--- change values
connidx=connidx+nprev*(nout-1);
neuroidx=neuroidx+nout;
structinfoidx=structinfoidx+nout+2;
weightsidx=weightsidx+(nout-1)*(nprev+1);
}
}
//+------------------------------------------------------------------+
//| This routine adds hidden layer to the high-level description of |
//| the network. |
//| It modifies Network.HLConnections and Network.HLNeurons and |
//| assumes that these arrays have enough place to store data. It |
//| accepts following parameters: |
//| Network - network |
//| ConnIdx - index of the first free entry in the |
//| HLConnections |
//| NeuroIdx - index of the first free entry in the |
//| HLNeurons |
//| StructInfoIdx- index of the first entry in the low level |
//| description of the current layer (in the |
//| StructInfo array) |
//| WeightsIdx - index of the first entry in the Weights |
//| array which corresponds to the current layer |
//| K - current layer index |
//| NPrev - number of neurons in the previous layer |
//| NCur - number of neurons in the current layer |
//| It modified Network and ConnIdx/NeuroIdx/StructInfoIdx/WeightsIdx|
//+------------------------------------------------------------------+
static void CMLPBase::HLAddHiddenLayer(CMultilayerPerceptron &network,
int &connidx,int &neuroidx,
int &structinfoidx,int &weightsidx,
const int k,const int nprev,
const int ncur)
{
//--- create variables
int i=0;
int j=0;
int neurooffs=0;
int connoffs=0;
//--- change values
neurooffs=m_hlm_nfieldwidth*neuroidx;
connoffs=m_hlconm_nfieldwidth*connidx;
for(i=0;i<=ncur-1;i++)
{
//--- change values
network.m_hlneurons[neurooffs+0]=k;
network.m_hlneurons[neurooffs+1]=i;
network.m_hlneurons[neurooffs+2]=structinfoidx+1+ncur+i;
network.m_hlneurons[neurooffs+3]=weightsidx+nprev+(nprev+1)*i;
neurooffs=neurooffs+m_hlm_nfieldwidth;
}
for(i=0;i<=nprev-1;i++)
{
for(j=0;j<=ncur-1;j++)
{
//--- change values
network.m_hlconnections[connoffs+0]=k-1;
network.m_hlconnections[connoffs+1]=i;
network.m_hlconnections[connoffs+2]=k;
network.m_hlconnections[connoffs+3]=j;
network.m_hlconnections[connoffs+4]=weightsidx+i+j*(nprev+1);
connoffs=connoffs+m_hlconm_nfieldwidth;
}
}
//--- change values
connidx=connidx+nprev*ncur;
neuroidx=neuroidx+ncur;
structinfoidx=structinfoidx+2*ncur+1;
weightsidx=weightsidx+ncur*(nprev+1);
}
//+------------------------------------------------------------------+
//| This function fills high level information about network created |
//| using internal MLPCreate() function. |
//| This function does NOT examine StructInfo for low level |
//| information, it just expects that network has following |
//| structure: |
//| input neuron \ |
//| ... | input layer |
//| input neuron / |
//| "-1" neuron \ |
//| biased summator | |
//| ... | |
//| biased summator | hidden layer(s), if there are |
//| activation function | exists any |
//| ... | |
//| activation function / |
//| "-1" neuron \ |
//| biased summator | output layer: |
//| ... | * we have NOut summators/activators|
//| biased summator | for regression networks |
//| activation function | * we have only NOut-1 summators and|
//| ... | no activators for classifiers |
//| activation function | * we have "0" neuron only when we |
//| "0" neuron / have classifier |
//+------------------------------------------------------------------+
static void CMLPBase::FillHighLevelInformation(CMultilayerPerceptron &network,
const int nin,const int nhid1,
const int nhid2,const int nout,
const bool iscls,const bool islinearout)
{
//--- create variables
int idxweights=0;
int idxstruct=0;
int idxneuro=0;
int idxconn=0;
//--- check
if(!CAp::Assert((iscls && islinearout) || !iscls,__FUNCTION__+": internal error"))
return;
//--- Preparations common to all types of networks
idxweights=0;
idxneuro=0;
idxstruct=0;
idxconn=0;
network.m_hlnetworktype=0;
//--- network without hidden layers
if(nhid1==0)
{
//--- allocation
ArrayResizeAL(network.m_hllayersizes,2);
//--- change values
network.m_hllayersizes[0]=nin;
network.m_hllayersizes[1]=nout;
//--- check
if(!iscls)
{
//--- allocation
ArrayResizeAL(network.m_hlconnections,m_hlconm_nfieldwidth*nin*nout);
ArrayResizeAL(network.m_hlneurons,m_hlm_nfieldwidth*(nin+nout));
network.m_hlnormtype=0;
}
else
{
//--- allocation
ArrayResizeAL(network.m_hlconnections,m_hlconm_nfieldwidth*nin*(nout-1));
ArrayResizeAL(network.m_hlneurons,m_hlm_nfieldwidth*(nin+nout));
network.m_hlnormtype=1;
}
//--- function call
HLAddInputLayer(network,idxconn,idxneuro,idxstruct,nin);
//--- function call
HLAddOutputLayer(network,idxconn,idxneuro,idxstruct,idxweights,1,nin,nout,iscls,islinearout);
//--- exit the function
return;
}
//--- network with one hidden layers
if(nhid2==0)
{
//--- allocation
ArrayResizeAL(network.m_hllayersizes,3);
//--- change values
network.m_hllayersizes[0]=nin;
network.m_hllayersizes[1]=nhid1;
network.m_hllayersizes[2]=nout;
//--- check
if(!iscls)
{
//--- allocation
ArrayResizeAL(network.m_hlconnections,m_hlconm_nfieldwidth*(nin*nhid1+nhid1*nout));
ArrayResizeAL(network.m_hlneurons,m_hlm_nfieldwidth*(nin+nhid1+nout));
network.m_hlnormtype=0;
}
else
{
//--- allocation
ArrayResizeAL(network.m_hlconnections,m_hlconm_nfieldwidth*(nin*nhid1+nhid1*(nout-1)));
ArrayResizeAL(network.m_hlneurons,m_hlm_nfieldwidth*(nin+nhid1+nout));
network.m_hlnormtype=1;
}
//--- function call
HLAddInputLayer(network,idxconn,idxneuro,idxstruct,nin);
//--- function call
HLAddHiddenLayer(network,idxconn,idxneuro,idxstruct,idxweights,1,nin,nhid1);
//--- function call
HLAddOutputLayer(network,idxconn,idxneuro,idxstruct,idxweights,2,nhid1,nout,iscls,islinearout);
//--- exit the function
return;
}
//--- Two hidden layers
ArrayResizeAL(network.m_hllayersizes,4);
//--- change values
network.m_hllayersizes[0]=nin;
network.m_hllayersizes[1]=nhid1;
network.m_hllayersizes[2]=nhid2;
network.m_hllayersizes[3]=nout;
//--- check
if(!iscls)
{
//--- allocation
ArrayResizeAL(network.m_hlconnections,m_hlconm_nfieldwidth*(nin*nhid1+nhid1*nhid2+nhid2*nout));
ArrayResizeAL(network.m_hlneurons,m_hlm_nfieldwidth*(nin+nhid1+nhid2+nout));
network.m_hlnormtype=0;
}
else
{
//--- allocation
ArrayResizeAL(network.m_hlconnections,m_hlconm_nfieldwidth*(nin*nhid1+nhid1*nhid2+nhid2*(nout-1)));
ArrayResizeAL(network.m_hlneurons,m_hlm_nfieldwidth*(nin+nhid1+nhid2+nout));
network.m_hlnormtype=1;
}
//--- function call
HLAddInputLayer(network,idxconn,idxneuro,idxstruct,nin);
//--- function call
HLAddHiddenLayer(network,idxconn,idxneuro,idxstruct,idxweights,1,nin,nhid1);
//--- function call
HLAddHiddenLayer(network,idxconn,idxneuro,idxstruct,idxweights,2,nhid1,nhid2);
//--- function call
HLAddOutputLayer(network,idxconn,idxneuro,idxstruct,idxweights,3,nhid2,nout,iscls,islinearout);
}
//+------------------------------------------------------------------+
//| Internal subroutine. |
//+------------------------------------------------------------------+
static void CMLPBase::MLPCreate(const int nin,const int nout,int &lsizes[],
int &ltypes[],int &lconnfirst[],int &lconnlast[],
const int layerscount,const bool isclsnet,
CMultilayerPerceptron &network)
{
//--- create variables
int i=0;
int j=0;
int ssize=0;
int ntotal=0;
int wcount=0;
int offs=0;
int nprocessed=0;
int wallocated=0;
//--- creating arrays
int localtemp[];
int lnfirst[];
int lnsyn[];
//--- Check
if(!CAp::Assert(layerscount>0,__FUNCTION__+": wrong parameters!"))
return;
//--- check
if(!CAp::Assert(ltypes[0]==-2,__FUNCTION__+": wrong LTypes[0] (must be -2)!"))
return;
for(i=0;i<=layerscount-1;i++)
{
//--- check
if(!CAp::Assert(lsizes[i]>0,__FUNCTION__+": wrong LSizes!"))
return;
//--- check
if(!CAp::Assert(lconnfirst[i]>=0 &&(lconnfirst[i]<i || i==0),__FUNCTION__+": wrong LConnFirst!"))
return;
//--- check
if(!CAp::Assert(lconnlast[i]>=lconnfirst[i]&&(lconnlast[i]<i || i==0),__FUNCTION__+": wrong LConnLast!"))
return;
}
//--- Build network geometry
ArrayResizeAL(lnfirst,layerscount);
ArrayResizeAL(lnsyn,layerscount);
//--- initialization
ntotal=0;
wcount=0;
//--- calculation
for(i=0;i<=layerscount-1;i++)
{
//--- Analyze connections.
//--- This code must throw an assertion in case of unknown LTypes[I]
lnsyn[i]=-1;
//--- check
if(ltypes[i]>=0 || ltypes[i]==-5)
{
lnsyn[i]=0;
for(j=lconnfirst[i];j<=lconnlast[i];j++)
lnsyn[i]=lnsyn[i]+lsizes[j];
}
else
{
//--- check
if((ltypes[i]==-2 || ltypes[i]==-3) || ltypes[i]==-4)
lnsyn[i]=0;
}
//--- check
if(!CAp::Assert(lnsyn[i]>=0,__FUNCTION__+": internal error #0!"))
return;
//--- Other info
lnfirst[i]=ntotal;
ntotal=ntotal+lsizes[i];
//--- check
if(ltypes[i]==0)
wcount=wcount+lnsyn[i]*lsizes[i];
}
ssize=7+ntotal*m_nfieldwidth;
//--- Allocate
ArrayResizeAL(network.m_structinfo,ssize);
ArrayResizeAL(network.m_weights,wcount);
//--- check
if(isclsnet)
{
//--- allocation
ArrayResizeAL(network.m_columnmeans,nin);
ArrayResizeAL(network.m_columnsigmas,nin);
}
else
{
//--- allocation
ArrayResizeAL(network.m_columnmeans,nin+nout);
ArrayResizeAL(network.m_columnsigmas,nin+nout);
}
//--- allocation
ArrayResizeAL(network.m_neurons,ntotal);
network.m_chunks.Resize(3*ntotal+1,m_chunksize);
ArrayResizeAL(network.m_nwbuf,MathMax(wcount,2*nout));
ArrayResizeAL(network.m_integerbuf,4);
ArrayResizeAL(network.m_dfdnet,ntotal);
ArrayResizeAL(network.m_x,nin);
ArrayResizeAL(network.m_y,nout);
ArrayResizeAL(network.m_derror,ntotal);
//--- Fill structure: global info
network.m_structinfo[0]=ssize;
network.m_structinfo[1]=nin;
network.m_structinfo[2]=nout;
network.m_structinfo[3]=ntotal;
network.m_structinfo[4]=wcount;
network.m_structinfo[5]=7;
//--- check
if(isclsnet)
network.m_structinfo[6]=1;
else
network.m_structinfo[6]=0;
//--- Fill structure: neuron connections
nprocessed=0;
wallocated=0;
//--- calculation
for(i=0;i<=layerscount-1;i++)
{
for(j=0;j<=lsizes[i]-1;j++)
{
offs=network.m_structinfo[5]+nprocessed*m_nfieldwidth;
network.m_structinfo[offs+0]=ltypes[i];
//--- check
if(ltypes[i]==0)
{
//--- Adaptive summator:
//--- * connections with weights to previous neurons
network.m_structinfo[offs+1]=lnsyn[i];
network.m_structinfo[offs+2]=lnfirst[lconnfirst[i]];
network.m_structinfo[offs+3]=wallocated;
wallocated=wallocated+lnsyn[i];
nprocessed=nprocessed+1;
}
//--- check
if(ltypes[i]>0 || ltypes[i]==-5)
{
//--- Activation layer:
//--- * each neuron connected to one (only one) of previous neurons.
//--- * no weights
network.m_structinfo[offs+1]=1;
network.m_structinfo[offs+2]=lnfirst[lconnfirst[i]]+j;
network.m_structinfo[offs+3]=-1;
nprocessed=nprocessed+1;
}
//--- check
if((ltypes[i]==-2 || ltypes[i]==-3) || ltypes[i]==-4)
nprocessed=nprocessed+1;
}
}
//--- check
if(!CAp::Assert(wallocated==wcount,__FUNCTION__+": internal error #1!"))
return;
//--- check
if(!CAp::Assert(nprocessed==ntotal,__FUNCTION__+": internal error #2!"))
return;
//--- Fill weights by small random values
//--- Initialize means and sigmas
for(i=0;i<=wcount-1;i++)
network.m_weights[i]=CMath::RandomReal()-0.5;
for(i=0;i<=nin-1;i++)
{
network.m_columnmeans[i]=0;
network.m_columnsigmas[i]=1;
}
//--- check
if(!isclsnet)
{
for(i=0;i<=nout-1;i++)
{
network.m_columnmeans[nin+i]=0;
network.m_columnsigmas[nin+i]=1;
}
}
}
//+------------------------------------------------------------------+
//| Internal subroutine for Hessian calculation. |
//| WARNING!!! Unspeakable math far beyong human capabilities :) |
//+------------------------------------------------------------------+
static void CMLPBase::MLPHessianBatchInternal(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int ssize,
const bool naturalerr,double &e,
double &grad[],CMatrixDouble &h)
{
//--- create variables
int nin=0;
int nout=0;
int wcount=0;
int ntotal=0;
int istart=0;
int i=0;
int j=0;
int k=0;
int kl=0;
int offs=0;
int n1=0;
int n2=0;
int w1=0;
int w2=0;
double s=0;
double t=0;
double v=0;
double et=0;
bool bflag;
double f=0;
double df=0;
double d2f=0;
double deidyj=0;
double mx=0;
double q=0;
double z=0;
double s2=0;
double expi=0;
double expj=0;
int i_=0;
int i1_=0;
//--- creating arrays
double x[];
double desiredy[];
double gt[];
double zeros[];
//--- create matrix
CMatrixDouble rx;
CMatrixDouble ry;
CMatrixDouble rdx;
CMatrixDouble rdy;
//--- initialization
e=0;
//--- function call
MLPProperties(network,nin,nout,wcount);
//--- initialization
ntotal=network.m_structinfo[3];
istart=network.m_structinfo[5];
//--- Prepare
ArrayResizeAL(x,nin);
ArrayResizeAL(desiredy,nout);
ArrayResizeAL(zeros,wcount);
ArrayResizeAL(gt,wcount);
rx.Resize(ntotal+nout,wcount);
ry.Resize(ntotal+nout,wcount);
rdx.Resize(ntotal+nout,wcount);
rdy.Resize(ntotal+nout,wcount);
//--- initialization
e=0;
for(i=0;i<=wcount-1;i++)
zeros[i]=0;
for(i_=0;i_<=wcount-1;i_++)
grad[i_]=zeros[i_];
for(i=0;i<=wcount-1;i++)
{
for(i_=0;i_<=wcount-1;i_++)
h[i].Set(i_,zeros[i_]);
}
//--- Process
for(k=0;k<=ssize-1;k++)
{
//--- Process vector with MLPGradN.
//--- Now Neurons,DFDNET and DError contains results of the last run.
for(i_=0;i_<=nin-1;i_++)
x[i_]=xy[k][i_];
//--- check
if(MLPIsSoftMax(network))
{
//--- class labels outputs
kl=(int)MathRound(xy[k][nin]);
for(i=0;i<=nout-1;i++)
{
//--- check
if(i==kl)
desiredy[i]=1;
else
desiredy[i]=0;
}
}
else
{
//--- real outputs
i1_=nin;
for(i_=0;i_<=nout-1;i_++)
desiredy[i_]=xy[k][i_+i1_];
}
//--- check
if(naturalerr)
MLPGradN(network,x,desiredy,et,gt);
else
MLPGrad(network,x,desiredy,et,gt);
//--- grad,error
e=e+et;
for(i_=0;i_<=wcount-1;i_++)
grad[i_]=grad[i_]+gt[i_];
//--- Hessian.
//--- Forward pass of the R-algorithm
for(i=0;i<=ntotal-1;i++)
{
offs=istart+i*m_nfieldwidth;
for(i_=0;i_<=wcount-1;i_++)
rx[i].Set(i_,zeros[i_]);
for(i_=0;i_<=wcount-1;i_++)
ry[i].Set(i_,zeros[i_]);
//--- check
if(network.m_structinfo[offs+0]>0 || network.m_structinfo[offs+0]==-5)
{
//--- Activation function
n1=network.m_structinfo[offs+2];
for(i_=0;i_<=wcount-1;i_++)
rx[i].Set(i_,ry[n1][i_]);
//--- calculation
v=network.m_dfdnet[i];
for(i_=0;i_<=wcount-1;i_++)
ry[i].Set(i_,v*rx[i][i_]);
continue;
}
//--- check
if(network.m_structinfo[offs+0]==0)
{
//--- Adaptive summator
n1=network.m_structinfo[offs+2];
n2=n1+network.m_structinfo[offs+1]-1;
w1=network.m_structinfo[offs+3];
w2=w1+network.m_structinfo[offs+1]-1;
//--- calculation
for(j=n1;j<=n2;j++)
{
v=network.m_weights[w1+j-n1];
for(i_=0;i_<=wcount-1;i_++)
rx[i].Set(i_,rx[i][i_]+v*ry[j][i_]);
rx[i].Set(w1+j-n1,rx[i][w1+j-n1]+network.m_neurons[j]);
}
for(i_=0;i_<=wcount-1;i_++)
ry[i].Set(i_,rx[i][i_]);
continue;
}
//--- check
if(network.m_structinfo[offs+0]<0)
{
bflag=true;
//--- check
if(network.m_structinfo[offs+0]==-2)
{
//--- input neuron,left unchanged
bflag=false;
}
//--- check
if(network.m_structinfo[offs+0]==-3)
{
//--- "-1" neuron,left unchanged
bflag=false;
}
//--- check
if(network.m_structinfo[offs+0]==-4)
{
//--- "0" neuron,left unchanged
bflag=false;
}
//--- check
if(!CAp::Assert(!bflag,__FUNCTION__+": internal error - unknown neuron type!"))
return;
continue;
}
}
//--- Hessian. Backward pass of the R-algorithm.
//--- Stage 1. Initialize RDY
for(i=0;i<=ntotal+nout-1;i++)
{
for(i_=0;i_<=wcount-1;i_++)
rdy[i].Set(i_,zeros[i_]);
}
//--- check
if(network.m_structinfo[6]==0)
{
//--- Standardisation.
//--- In context of the Hessian calculation standardisation
//--- is considered as additional layer with weightless
//--- activation function:
//--- F(NET) :=Sigma*NET
//--- So we add one more layer to forward pass,and
//--- make forward/backward pass through this layer.
for(i=0;i<=nout-1;i++)
{
n1=ntotal-nout+i;
n2=ntotal+i;
//--- Forward pass from N1 to N2
for(i_=0;i_<=wcount-1;i_++)
rx[n2].Set(i_,ry[n1][i_]);
v=network.m_columnsigmas[nin+i];
for(i_=0;i_<=wcount-1;i_++)
ry[n2].Set(i_,v*rx[n2][i_]);
//--- Initialization of RDY
for(i_=0;i_<=wcount-1;i_++)
rdy[n2].Set(i_,ry[n2][i_]);
//--- Backward pass from N2 to N1:
//--- 1. Calculate R(dE/dX).
//--- 2. No R(dE/dWij) is needed since weight of activation neuron
//--- is fixed to 1. So we can update R(dE/dY) for
//--- the connected neuron (note that Vij=0,Wij=1)
df=network.m_columnsigmas[nin+i];
for(i_=0;i_<=wcount-1;i_++)
rdx[n2].Set(i_,df*rdy[n2][i_]);
for(i_=0;i_<=wcount-1;i_++)
rdy[n1].Set(i_,rdy[n1][i_]+rdx[n2][i_]);
}
}
else
{
//--- Softmax.
//--- Initialize RDY using generalized expression for ei'(yi)
//--- (see expression (9) from p. 5 of "Fast Exact Multiplication by the Hessian").
//--- When we are working with softmax network,generalized
//--- expression for ei'(yi) is used because softmax
//--- normalization leads to ei,which depends on all y's
if(naturalerr)
{
//--- softmax + cross-entropy.
//--- We have:
//--- S=sum(exp(yk)),
//--- ei=sum(trn)*exp(yi)/S-trn_i
//--- j=i: d(ei)/d(yj)=T*exp(yi)*(S-exp(yi))/S^2
//--- j<>i: d(ei)/d(yj)=-T*exp(yi)*exp(yj)/S^2
t=0;
for(i=0;i<=nout-1;i++)
t=t+desiredy[i];
mx=network.m_neurons[ntotal-nout];
//--- calculation
for(i=0;i<=nout-1;i++)
mx=MathMax(mx,network.m_neurons[ntotal-nout+i]);
s=0;
for(i=0;i<=nout-1;i++)
{
network.m_nwbuf[i]=MathExp(network.m_neurons[ntotal-nout+i]-mx);
s=s+network.m_nwbuf[i];
}
//--- calculation
for(i=0;i<=nout-1;i++)
{
for(j=0;j<=nout-1;j++)
{
//--- check
if(j==i)
{
deidyj=t*network.m_nwbuf[i]*(s-network.m_nwbuf[i])/CMath::Sqr(s);
for(i_=0;i_<=wcount-1;i_++)
rdy[ntotal-nout+i].Set(i_,rdy[ntotal-nout+i][i_]+deidyj*ry[ntotal-nout+i][i_]);
}
else
{
deidyj=-(t*network.m_nwbuf[i]*network.m_nwbuf[j]/CMath::Sqr(s));
for(i_=0;i_<=wcount-1;i_++)
rdy[ntotal-nout+i].Set(i_,rdy[ntotal-nout+i][i_]+deidyj*ry[ntotal-nout+j][i_]);
}
}
}
}
else
{
//--- For a softmax + squared error we have expression
//--- far beyond human imagination so we dont even try
//--- to comment on it. Just enjoy the code...
//--- P.S. That's why "natural error" is called "natural" -
//--- compact beatiful expressions,fast code....
mx=network.m_neurons[ntotal-nout];
for(i=0;i<=nout-1;i++)
mx=MathMax(mx,network.m_neurons[ntotal-nout+i]);
//--- calculation
s=0;
s2=0;
for(i=0;i<=nout-1;i++)
{
network.m_nwbuf[i]=MathExp(network.m_neurons[ntotal-nout+i]-mx);
s=s+network.m_nwbuf[i];
s2=s2+CMath::Sqr(network.m_nwbuf[i]);
}
//--- calculation
q=0;
for(i=0;i<=nout-1;i++)
q=q+(network.m_y[i]-desiredy[i])*network.m_nwbuf[i];
for(i=0;i<=nout-1;i++)
{
//--- change values
z=-q+(network.m_y[i]-desiredy[i])*s;
expi=network.m_nwbuf[i];
for(j=0;j<=nout-1;j++)
{
expj=network.m_nwbuf[j];
//--- check
if(j==i)
deidyj=expi/CMath::Sqr(s)*((z+expi)*(s-2*expi)/s+expi*s2/CMath::Sqr(s));
else
deidyj=expi*expj/CMath::Sqr(s)*(s2/CMath::Sqr(s)-2*z/s-(expi+expj)/s+(network.m_y[i]-desiredy[i])-(network.m_y[j]-desiredy[j]));
for(i_=0;i_<=wcount-1;i_++)
rdy[ntotal-nout+i].Set(i_,rdy[ntotal-nout+i][i_]+deidyj*ry[ntotal-nout+j][i_]);
}
}
}
}
//--- Hessian. Backward pass of the R-algorithm
//--- Stage 2. Process.
for(i=ntotal-1;i>=0;i--)
{
//--- Possible variants:
//--- 1. Activation function
//--- 2. Adaptive summator
//--- 3. Special neuron
offs=istart+i*m_nfieldwidth;
//--- check
if(network.m_structinfo[offs+0]>0 || network.m_structinfo[offs+0]==-5)
{
n1=network.m_structinfo[offs+2];
//--- First,calculate R(dE/dX).
MLPActivationFunction(network.m_neurons[n1],network.m_structinfo[offs+0],f,df,d2f);
v=d2f*network.m_derror[i];
for(i_=0;i_<=wcount-1;i_++)
rdx[i].Set(i_,df*rdy[i][i_]);
for(i_=0;i_<=wcount-1;i_++)
rdx[i].Set(i_,rdx[i][i_]+v*rx[i][i_]);
//--- No R(dE/dWij) is needed since weight of activation neuron
//--- is fixed to 1.
//--- So we can update R(dE/dY) for the connected neuron.
//--- (note that Vij=0,Wij=1)
for(i_=0;i_<=wcount-1;i_++)
rdy[n1].Set(i_,rdy[n1][i_]+rdx[i][i_]);
continue;
}
//--- check
if(network.m_structinfo[offs+0]==0)
{
//--- Adaptive summator
n1=network.m_structinfo[offs+2];
n2=n1+network.m_structinfo[offs+1]-1;
w1=network.m_structinfo[offs+3];
w2=w1+network.m_structinfo[offs+1]-1;
//--- First,calculate R(dE/dX).
for(i_=0;i_<=wcount-1;i_++)
rdx[i].Set(i_,rdy[i][i_]);
//--- Then,calculate R(dE/dWij)
for(j=w1;j<=w2;j++)
{
v=network.m_neurons[n1+j-w1];
for(i_=0;i_<=wcount-1;i_++)
h[j].Set(i_,h[j][i_]+v*rdx[i][i_]);
//--- calculation
v=network.m_derror[i];
for(i_=0;i_<=wcount-1;i_++)
h[j].Set(i_,h[j][i_]+v*ry[n1+j-w1][i_]);
}
//--- And finally,update R(dE/dY) for connected neurons.
for(j=w1;j<=w2;j++)
{
v=network.m_weights[j];
for(i_=0;i_<=wcount-1;i_++)
rdy[n1+j-w1].Set(i_,rdy[n1+j-w1][i_]+v*rdx[i][i_]);
rdy[n1+j-w1].Set(j,rdy[n1+j-w1][j]+network.m_derror[i]);
}
continue;
}
//--- check
if(network.m_structinfo[offs+0]<0)
{
bflag=false;
//--- check
if((network.m_structinfo[offs+0]==-2 || network.m_structinfo[offs+0]==-3) || network.m_structinfo[offs+0]==-4)
{
//--- Special neuron type,no back-propagation required
bflag=true;
}
//--- check
if(!CAp::Assert(bflag,__FUNCTION__+": unknown neuron type!"))
return;
continue;
}
}
}
}
//+------------------------------------------------------------------+
//| Internal subroutine |
//| Network must be processed by MLPProcess on X |
//+------------------------------------------------------------------+
static void CMLPBase::MLPInternalCalculateGradient(CMultilayerPerceptron &network,
double &neurons[],
double &weights[],
double &derror[],
double &grad[],
const bool naturalerrorfunc)
{
//--- create variables
int i=0;
int n1=0;
int n2=0;
int w1=0;
int w2=0;
int ntotal=0;
int istart=0;
int nin=0;
int nout=0;
int offs=0;
double dedf=0;
double dfdnet=0;
double v=0;
double fown=0;
double deown=0;
double net=0;
double mx=0;
bool bflag;
int i_=0;
int i1_=0;
//--- Read network geometry
nin=network.m_structinfo[1];
nout=network.m_structinfo[2];
ntotal=network.m_structinfo[3];
istart=network.m_structinfo[5];
//--- Pre-processing of dError/dOut:
//--- from dError/dOut(normalized) to dError/dOut(non-normalized)
if(!CAp::Assert(network.m_structinfo[6]==0 || network.m_structinfo[6]==1,__FUNCTION__+": unknown normalization type!"))
return;
//--- check
if(network.m_structinfo[6]==1)
{
//--- Softmax
if(!naturalerrorfunc)
{
mx=network.m_neurons[ntotal-nout];
for(i=0;i<=nout-1;i++)
mx=MathMax(mx,network.m_neurons[ntotal-nout+i]);
net=0;
for(i=0;i<=nout-1;i++)
{
network.m_nwbuf[i]=MathExp(network.m_neurons[ntotal-nout+i]-mx);
net=net+network.m_nwbuf[i];
}
//--- calculation
i1_=-(ntotal-nout);
v=0.0;
for(i_=ntotal-nout;i_<=ntotal-1;i_++)
v+=network.m_derror[i_]*network.m_nwbuf[i_+i1_];
for(i=0;i<=nout-1;i++)
{
fown=network.m_nwbuf[i];
deown=network.m_derror[ntotal-nout+i];
network.m_nwbuf[nout+i]=(-v+deown*fown+deown*(net-fown))*fown/CMath::Sqr(net);
}
for(i=0;i<=nout-1;i++)
network.m_derror[ntotal-nout+i]=network.m_nwbuf[nout+i];
}
}
else
{
//--- Un-standardisation
for(i=0;i<=nout-1;i++)
network.m_derror[ntotal-nout+i]=network.m_derror[ntotal-nout+i]*network.m_columnsigmas[nin+i];
}
//--- Backpropagation
for(i=ntotal-1;i>=0;i--)
{
//--- Extract info
offs=istart+i*m_nfieldwidth;
//--- check
if(network.m_structinfo[offs+0]>0 || network.m_structinfo[offs+0]==-5)
{
//--- Activation function
dedf=network.m_derror[i];
dfdnet=network.m_dfdnet[i];
derror[network.m_structinfo[offs+2]]=derror[network.m_structinfo[offs+2]]+dedf*dfdnet;
continue;
}
//--- check
if(network.m_structinfo[offs+0]==0)
{
//--- Adaptive summator
n1=network.m_structinfo[offs+2];
n2=n1+network.m_structinfo[offs+1]-1;
w1=network.m_structinfo[offs+3];
w2=w1+network.m_structinfo[offs+1]-1;
dedf=network.m_derror[i];
dfdnet=1.0;
v=dedf*dfdnet;
i1_=n1-w1;
//--- calculation
for(i_=w1;i_<=w2;i_++)
grad[i_]=v*neurons[i_+i1_];
i1_=w1-n1;
for(i_=n1;i_<=n2;i_++)
derror[i_]=derror[i_]+v*weights[i_+i1_];
continue;
}
//--- check
if(network.m_structinfo[offs+0]<0)
{
bflag=false;
//--- check
if((network.m_structinfo[offs+0]==-2 || network.m_structinfo[offs+0]==-3) || network.m_structinfo[offs+0]==-4)
{
//--- Special neuron type,no back-propagation required
bflag=true;
}
//--- check
if(!CAp::Assert(bflag,__FUNCTION__+": unknown neuron type!"))
return;
continue;
}
}
}
//+------------------------------------------------------------------+
//| Internal subroutine, chunked gradient |
//+------------------------------------------------------------------+
static void CMLPBase::MLPChunkedGradient(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int cstart,
const int csize,double &e,
double &grad[],const bool naturalerrorfunc)
{
//--- create variables
int i=0;
int j=0;
int k=0;
int kl=0;
int n1=0;
int n2=0;
int w1=0;
int w2=0;
int c1=0;
int c2=0;
int ntotal=0;
int nin=0;
int nout=0;
int offs=0;
double f=0;
double df=0;
double d2f=0;
double v=0;
double s=0;
double fown=0;
double deown=0;
double net=0;
double lnnet=0;
double mx=0;
bool bflag;
int istart=0;
int ineurons=0;
int idfdnet=0;
int iderror=0;
int izeros=0;
int i_=0;
int i1_=0;
//--- Read network geometry,prepare data
nin=network.m_structinfo[1];
nout=network.m_structinfo[2];
ntotal=network.m_structinfo[3];
istart=network.m_structinfo[5];
c1=cstart;
c2=cstart+csize-1;
ineurons=0;
idfdnet=ntotal;
iderror=2*ntotal;
izeros=3*ntotal;
for(j=0;j<=csize-1;j++)
network.m_chunks[izeros].Set(j,0);
//--- Forward pass:
//--- 1. Load inputs from XY to Chunks[0:NIn-1,0:CSize-1]
//--- 2. Forward pass
for(i=0;i<=nin-1;i++)
{
for(j=0;j<=csize-1;j++)
{
//--- check
if(network.m_columnsigmas[i]!=0.0)
network.m_chunks[i].Set(j,(xy[c1+j][i]-network.m_columnmeans[i])/network.m_columnsigmas[i]);
else
network.m_chunks[i].Set(j,xy[c1+j][i]-network.m_columnmeans[i]);
}
}
for(i=0;i<=ntotal-1;i++)
{
offs=istart+i*m_nfieldwidth;
//--- check
if(network.m_structinfo[offs+0]>0 || network.m_structinfo[offs+0]==-5)
{
//--- Activation function:
//--- * calculate F vector,F(i)=F(NET(i))
n1=network.m_structinfo[offs+2];
for(i_=0;i_<=csize-1;i_++)
network.m_chunks[i].Set(i_,network.m_chunks[n1][i_]);
for(j=0;j<=csize-1;j++)
{
//--- function call
MLPActivationFunction(network.m_chunks[i][j],network.m_structinfo[offs+0],f,df,d2f);
//--- change values
network.m_chunks[i].Set(j,f);
network.m_chunks[idfdnet+i].Set(j,df);
}
continue;
}
//--- check
if(network.m_structinfo[offs+0]==0)
{
//--- Adaptive summator:
//--- * calculate NET vector,NET(i)=SUM(W(j,i)*Neurons(j),j=N1..N2)
n1=network.m_structinfo[offs+2];
n2=n1+network.m_structinfo[offs+1]-1;
w1=network.m_structinfo[offs+3];
w2=w1+network.m_structinfo[offs+1]-1;
//--- calculation
for(i_=0;i_<=csize-1;i_++)
network.m_chunks[i].Set(i_,network.m_chunks[izeros][i_]);
for(j=n1;j<=n2;j++)
{
v=network.m_weights[w1+j-n1];
for(i_=0;i_<=csize-1;i_++)
network.m_chunks[i].Set(i_,network.m_chunks[i][i_]+v*network.m_chunks[j][i_]);
}
continue;
}
//--- check
if(network.m_structinfo[offs+0]<0)
{
bflag=false;
//--- check
if(network.m_structinfo[offs+0]==-2)
{
//--- input neuron,left unchanged
bflag=true;
}
//--- check
if(network.m_structinfo[offs+0]==-3)
{
//--- "-1" neuron
for(k=0;k<=csize-1;k++)
network.m_chunks[i].Set(k,-1);
bflag=true;
}
//--- check
if(network.m_structinfo[offs+0]==-4)
{
//--- "0" neuron
for(k=0;k<=csize-1;k++)
network.m_chunks[i].Set(k,0);
bflag=true;
}
//--- check
if(!CAp::Assert(bflag,__FUNCTION__+": internal error - unknown neuron type!"))
return;
continue;
}
}
//--- Post-processing,error,dError/dOut
for(i=0;i<=ntotal-1;i++)
{
for(i_=0;i_<=csize-1;i_++)
network.m_chunks[iderror+i].Set(i_,network.m_chunks[izeros][i_]);
}
//--- check
if(!CAp::Assert(network.m_structinfo[6]==0 || network.m_structinfo[6]==1,__FUNCTION__+": unknown normalization type!"))
return;
//--- check
if(network.m_structinfo[6]==1)
{
//--- Softmax output,classification network.
//--- For each K=0..CSize-1 do:
//--- 1. place exp(outputs[k]) to NWBuf[0:NOut-1]
//--- 2. place sum(exp(..)) to NET
//--- 3. calculate dError/dOut and place it to the second block of Chunks
for(k=0;k<=csize-1;k++)
{
//--- Normalize
mx=network.m_chunks[ntotal-nout][k];
for(i=1;i<=nout-1;i++)
mx=MathMax(mx,network.m_chunks[ntotal-nout+i][k]);
net=0;
for(i=0;i<=nout-1;i++)
{
network.m_nwbuf[i]=MathExp(network.m_chunks[ntotal-nout+i][k]-mx);
net=net+network.m_nwbuf[i];
}
//--- Calculate error function and dError/dOut
if(naturalerrorfunc)
{
//--- Natural error func.
s=1;
lnnet=MathLog(net);
kl=(int)MathRound(xy[cstart+k][nin]);
//--- calculation
for(i=0;i<=nout-1;i++)
{
//--- check
if(i==kl)
v=1;
else
v=0;
network.m_chunks[iderror+ntotal-nout+i].Set(k,s*network.m_nwbuf[i]/net-v);
e=e+SafeCrossEntropy(v,network.m_nwbuf[i]/net);
}
}
else
{
//--- Least squares error func
//--- Error,dError/dOut(normalized)
kl=(int)MathRound(xy[cstart+k][nin]);
for(i=0;i<=nout-1;i++)
{
//--- check
if(i==kl)
v=network.m_nwbuf[i]/net-1;
else
v=network.m_nwbuf[i]/net;
network.m_nwbuf[nout+i]=v;
e=e+CMath::Sqr(v)/2;
}
//--- From dError/dOut(normalized) to dError/dOut(non-normalized)
i1_=-nout;
v=0.0;
for(i_=nout;i_<=2*nout-1;i_++)
v+=network.m_nwbuf[i_]*network.m_nwbuf[i_+i1_];
//--- calculation
for(i=0;i<=nout-1;i++)
{
fown=network.m_nwbuf[i];
deown=network.m_nwbuf[nout+i];
network.m_chunks[iderror+ntotal-nout+i].Set(k,(-v+deown*fown+deown*(net-fown))*fown/CMath::Sqr(net));
}
}
}
}
else
{
//--- Normal output,regression network
//--- For each K=0..CSize-1 do:
//--- 1. calculate dError/dOut and place it to the second block of Chunks
for(i=0;i<=nout-1;i++)
{
for(j=0;j<=csize-1;j++)
{
v=network.m_chunks[ntotal-nout+i][j]*network.m_columnsigmas[nin+i]+network.m_columnmeans[nin+i]-xy[cstart+j][nin+i];
network.m_chunks[iderror+ntotal-nout+i].Set(j,v*network.m_columnsigmas[nin+i]);
e=e+CMath::Sqr(v)/2;
}
}
}
//--- Backpropagation
for(i=ntotal-1;i>=0;i--)
{
//--- Extract info
offs=istart+i*m_nfieldwidth;
//--- check
if(network.m_structinfo[offs+0]>0 || network.m_structinfo[offs+0]==-5)
{
//--- Activation function
n1=network.m_structinfo[offs+2];
for(k=0;k<=csize-1;k++)
network.m_chunks[iderror+i].Set(k,network.m_chunks[iderror+i][k]*network.m_chunks[idfdnet+i][k]);
for(i_=0;i_<=csize-1;i_++)
network.m_chunks[iderror+n1].Set(i_,network.m_chunks[iderror+n1][i_]+network.m_chunks[iderror+i][i_]);
continue;
}
//--- check
if(network.m_structinfo[offs+0]==0)
{
//--- "Normal" activation function
n1=network.m_structinfo[offs+2];
n2=n1+network.m_structinfo[offs+1]-1;
w1=network.m_structinfo[offs+3];
w2=w1+network.m_structinfo[offs+1]-1;
//--- calculation
for(j=w1;j<=w2;j++)
{
v=0.0;
for(i_=0;i_<=csize-1;i_++)
v+=network.m_chunks[n1+j-w1][i_]*network.m_chunks[iderror+i][i_];
grad[j]=grad[j]+v;
}
//--- calculation
for(j=n1;j<=n2;j++)
{
v=network.m_weights[w1+j-n1];
for(i_=0;i_<=csize-1;i_++)
network.m_chunks[iderror+j].Set(i_,network.m_chunks[iderror+j][i_]+v*network.m_chunks[iderror+i][i_]);
}
continue;
}
//--- check
if(network.m_structinfo[offs+0]<0)
{
bflag=false;
//--- check
if((network.m_structinfo[offs+0]==-2 || network.m_structinfo[offs+0]==-3) || network.m_structinfo[offs+0]==-4)
{
//--- Special neuron type,no back-propagation required
bflag=true;
}
//--- check
if(!CAp::Assert(bflag,__FUNCTION__+": unknown neuron type!"))
return;
continue;
}
}
}
//+------------------------------------------------------------------+
//| Returns T*Ln(T/Z), guarded against overflow/underflow. |
//| Internal subroutine. |
//+------------------------------------------------------------------+
static double CMLPBase::SafeCrossEntropy(const double t,const double z)
{
//--- create variables
double result=0;
double r=0;
//--- check
if(t==0.0)
result=0;
else
{
//--- check
if(MathAbs(z)>1.0)
{
//--- Shouldn't be the case with softmax,
//--- but we just want to be sure.
if(t/z==0.0)
r=CMath::m_minrealnumber;
else
r=t/z;
}
else
{
//--- Normal case
if(z==0.0 || MathAbs(t)>=CMath::m_maxrealnumber*MathAbs(z))
r=CMath::m_maxrealnumber;
else
r=t/z;
}
//--- get result
result=t*MathLog(r);
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Auxiliary class for CLogit |
//+------------------------------------------------------------------+
class CLogitModel
{
public:
double m_w[];
//--- constructor, destructor
CLogitModel(void);
~CLogitModel(void);
//--- copy
void Copy(CLogitModel &obj);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CLogitModel::CLogitModel(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CLogitModel::~CLogitModel(void)
{
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CLogitModel::Copy(CLogitModel &obj)
{
//--- copy array
ArrayCopy(m_w,obj.m_w);
}
//+------------------------------------------------------------------+
//| This class is a shell for class CLogitModel |
//+------------------------------------------------------------------+
class CLogitModelShell
{
private:
CLogitModel m_innerobj;
public:
//--- constructors, destructor
CLogitModelShell(void);
CLogitModelShell(CLogitModel &obj);
~CLogitModelShell(void);
//--- method
CLogitModel *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CLogitModelShell::CLogitModelShell(void)
{
}
//+------------------------------------------------------------------+
//| Copy constructor |
//+------------------------------------------------------------------+
CLogitModelShell::CLogitModelShell(CLogitModel &obj)
{
//--- copy
m_innerobj.Copy(obj);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CLogitModelShell::~CLogitModelShell(void)
{
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CLogitModel *CLogitModelShell::GetInnerObj(void)
{
//--- return result
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Auxiliary class for CLogit |
//+------------------------------------------------------------------+
class CLogitMCState
{
public:
//--- variables
bool m_brackt;
bool m_stage1;
int m_infoc;
double m_dg;
double m_dgm;
double m_dginit;
double m_dgtest;
double m_dgx;
double m_dgxm;
double m_dgy;
double m_dgym;
double m_finit;
double m_ftest1;
double m_fm;
double m_fx;
double m_fxm;
double m_fy;
double m_fym;
double m_stx;
double m_sty;
double m_stmin;
double m_stmax;
double m_width;
double m_width1;
double m_xtrapf;
//--- constructor, destructor
CLogitMCState(void);
~CLogitMCState(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CLogitMCState::CLogitMCState(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CLogitMCState::~CLogitMCState(void)
{
}
//+------------------------------------------------------------------+
//| MNLReport structure contains information about training process: |
//| * NGrad - number of gradient calculations |
//| * NHess - number of Hessian calculations |
//+------------------------------------------------------------------+
class CMNLReport
{
public:
//--- variables
int m_ngrad;
int m_nhess;
//--- constructor, destructor
CMNLReport(void);
~CMNLReport(void);
//--- copy
void Copy(CMNLReport &obj);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMNLReport::CMNLReport(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMNLReport::~CMNLReport(void)
{
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CMNLReport::Copy(CMNLReport &obj)
{
//--- copy variables
m_ngrad=obj.m_ngrad;
m_nhess=obj.m_nhess;
}
//+------------------------------------------------------------------+
//| MNLReport structure contains information about training process: |
//| * NGrad - number of gradient calculations |
//| * NHess - number of Hessian calculations |
//+------------------------------------------------------------------+
class CMNLReportShell
{
private:
CMNLReport m_innerobj;
public:
//--- constructors, destructor
CMNLReportShell(void);
CMNLReportShell(CMNLReport &obj);
~CMNLReportShell(void);
//--- methods
int GetNGrad(void);
void SetNGrad(const int i);
int GetNHess(void);
void SetNHess(const int i);
CMNLReport *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMNLReportShell::CMNLReportShell(void)
{
}
//+------------------------------------------------------------------+
//| Copy constructor |
//+------------------------------------------------------------------+
CMNLReportShell::CMNLReportShell(CMNLReport &obj)
{
//--- copy
m_innerobj.Copy(obj);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMNLReportShell::~CMNLReportShell(void)
{
}
//+------------------------------------------------------------------+
//| Returns the value of the variable ngrad |
//+------------------------------------------------------------------+
int CMNLReportShell::GetNGrad(void)
{
//--- return result
return(m_innerobj.m_ngrad);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable ngrad |
//+------------------------------------------------------------------+
void CMNLReportShell::SetNGrad(const int i)
{
//--- change value
m_innerobj.m_ngrad=i;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable nhess |
//+------------------------------------------------------------------+
int CMNLReportShell::GetNHess(void)
{
//--- return result
return(m_innerobj.m_nhess);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable nhess |
//+------------------------------------------------------------------+
void CMNLReportShell::SetNHess(const int i)
{
//--- change value
m_innerobj.m_nhess=i;
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CMNLReport *CMNLReportShell::GetInnerObj(void)
{
//--- return result
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Class logit model |
//+------------------------------------------------------------------+
class CLogit
{
private:
//--- private methods
static void MNLIExp(double &w[],double &x[]);
static void MNLAllErrors(CLogitModel &lm,CMatrixDouble &xy,const int npoints,double &relcls,double &avgce,double &rms,double &avg,double &avgrel);
static void MNLMCSrch(const int n,double &x[],double &f,double &g[],double &s[],double &stp,int &info,int &nfev,double &wa[],CLogitMCState &state,int &stage);
static void MNLMCStep(double &stx,double &fx,double &dx,double &sty,double &fy,double &dy,double &stp,const double fp,const double dp,bool &brackt,const double stmin,const double stmax,int &info);
public:
//--- variables
static const double m_xtol;
static const double m_ftol;
static const double m_gtol;
static const int m_maxfev;
static const double m_stpmin;
static const double m_stpmax;
static const int m_logitvnum;
//--- constructor, destructor
CLogit(void);
~CLogit(void);
//--- public methods
static void MNLTrainH(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,int &info,CLogitModel &lm,CMNLReport &rep);
static void MNLProcess(CLogitModel &lm,double &x[],double &y[]);
static void MNLProcessI(CLogitModel &lm,double &x[],double &y[]);
static void MNLUnpack(CLogitModel &lm,CMatrixDouble &a,int &nvars,int &nclasses);
static void MNLPack(CMatrixDouble &a,const int nvars,const int nclasses,CLogitModel &lm);
static void MNLCopy(CLogitModel &lm1,CLogitModel &lm2);
static double MNLAvgCE(CLogitModel &lm,CMatrixDouble &xy,const int npoints);
static double MNLRelClsError(CLogitModel &lm,CMatrixDouble &xy,const int npoints);
static double MNLRMSError(CLogitModel &lm,CMatrixDouble &xy,const int npoints);
static double MNLAvgError(CLogitModel &lm,CMatrixDouble &xy,const int npoints);
static double MNLAvgRelError(CLogitModel &lm,CMatrixDouble &xy,const int ssize);
static int MNLClsError(CLogitModel &lm,CMatrixDouble &xy,const int npoints);
};
//+------------------------------------------------------------------+
//| Initialize constants |
//+------------------------------------------------------------------+
const double CLogit::m_xtol=100*CMath::m_machineepsilon;
const double CLogit::m_ftol=0.0001;
const double CLogit::m_gtol=0.3;
const int CLogit::m_maxfev=20;
const double CLogit::m_stpmin=1.0E-2;
const double CLogit::m_stpmax=1.0E5;
const int CLogit::m_logitvnum=6;
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CLogit::CLogit(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CLogit::~CLogit(void)
{
}
//+------------------------------------------------------------------+
//| This subroutine trains logit model. |
//| INPUT PARAMETERS: |
//| XY - training set, array[0..NPoints-1,0..NVars] |
//| First NVars columns store values of |
//| independent variables, next column stores |
//| number of class (from 0 to NClasses-1) which |
//| dataset element belongs to. Fractional values|
//| are rounded to nearest integer. |
//| NPoints - training set size, NPoints>=1 |
//| NVars - number of independent variables, NVars>=1 |
//| NClasses - number of classes, NClasses>=2 |
//| OUTPUT PARAMETERS: |
//| Info - return code: |
//| * -2, if there is a point with class number |
//| outside of [0..NClasses-1]. |
//| * -1, if incorrect parameters was passed |
//| (NPoints<NVars+2, NVars<1, NClasses<2).|
//| * 1, if task has been solved |
//| LM - model built |
//| Rep - training report |
//+------------------------------------------------------------------+
static void CLogit::MNLTrainH(CMatrixDouble &xy,const int npoints,
const int nvars,const int nclasses,
int &info,CLogitModel &lm,CMNLReport &rep)
{
//--- create variables
int i=0;
int j=0;
int k=0;
int ssize=0;
bool allsame;
int offs=0;
double threshold=0;
double wminstep=0;
double decay=0;
int wdim=0;
int expoffs=0;
double v=0;
double s=0;
int nin=0;
int nout=0;
int wcount=0;
double e=0;
bool spd;
double wstep=0;
int mcstage=0;
int mcinfo=0;
int mcnfev=0;
int solverinfo=0;
int i_=0;
int i1_=0;
//--- creating arrays
double g[];
double x[];
double y[];
double wbase[];
double wdir[];
double work[];
//--- create matrix
CMatrixDouble h;
//--- create objects of classes
CLogitMCState mcstate;
CDenseSolverReport solverrep;
CMultilayerPerceptron network;
//--- initialization
info=0;
threshold=1000*CMath::m_machineepsilon;
wminstep=0.001;
decay=0.001;
//--- Test for inputs
if((npoints<nvars+2 || nvars<1) || nclasses<2)
{
info=-1;
return;
}
for(i=0;i<=npoints-1;i++)
{
//--- check
if((int)MathRound(xy[i][nvars])<0 || (int)MathRound(xy[i][nvars])>=nclasses)
{
info=-2;
return;
}
}
//--- change value
info=1;
//--- Initialize data
rep.m_ngrad=0;
rep.m_nhess=0;
//--- Allocate array
wdim=(nvars+1)*(nclasses-1);
offs=5;
expoffs=offs+wdim;
ssize=5+(nvars+1)*(nclasses-1)+nclasses;
//--- allocation
ArrayResizeAL(lm.m_w,ssize);
//--- change values
lm.m_w[0]=ssize;
lm.m_w[1]=m_logitvnum;
lm.m_w[2]=nvars;
lm.m_w[3]=nclasses;
lm.m_w[4]=offs;
//--- Degenerate case: all outputs are equal
allsame=true;
for(i=1;i<=npoints-1;i++)
{
//--- check
if((int)MathRound(xy[i][nvars])!=(int)MathRound(xy[i-1][nvars]))
allsame=false;
}
//--- check
if(allsame)
{
for(i=0;i<=(nvars+1)*(nclasses-1)-1;i++)
lm.m_w[offs+i]=0;
//--- change values
v=-(2*MathLog(CMath::m_minrealnumber));
k=(int)MathRound(xy[0][nvars]);
//--- check
if(k==nclasses-1)
{
for(i=0;i<=nclasses-2;i++)
lm.m_w[offs+i*(nvars+1)+nvars]=-v;
}
else
{
for(i=0;i<=nclasses-2;i++)
{
//--- check
if(i==k)
lm.m_w[offs+i*(nvars+1)+nvars]=v;
else
lm.m_w[offs+i*(nvars+1)+nvars]=0;
}
}
//--- exit the function
return;
}
//--- General case.
//--- Prepare task and network. Allocate space.
CMLPBase::MLPCreateC0(nvars,nclasses,network);
//--- function call
CMLPBase::MLPInitPreprocessor(network,xy,npoints);
//--- function call
CMLPBase::MLPProperties(network,nin,nout,wcount);
for(i=0;i<=wcount-1;i++)
network.m_weights[i]=(2*CMath::RandomReal()-1)/nvars;
//--- allocation
ArrayResizeAL(g,wcount);
h.Resize(wcount,wcount);
ArrayResizeAL(wbase,wcount);
ArrayResizeAL(wdir,wcount);
ArrayResizeAL(work,wcount);
//--- First stage: optimize in gradient direction.
for(k=0;k<=wcount/3+10;k++)
{
//--- Calculate gradient in starting point
CMLPBase::MLPGradNBatch(network,xy,npoints,e,g);
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=network.m_weights[i_]*network.m_weights[i_];
//--- change value
e=e+0.5*decay*v;
for(i_=0;i_<=wcount-1;i_++)
g[i_]=g[i_]+decay*network.m_weights[i_];
rep.m_ngrad=rep.m_ngrad+1;
//--- Setup optimization scheme
for(i_=0;i_<=wcount-1;i_++)
wdir[i_]=-g[i_];
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=wdir[i_]*wdir[i_];
//--- change values
wstep=MathSqrt(v);
v=1/MathSqrt(v);
for(i_=0;i_<=wcount-1;i_++)
wdir[i_]=v*wdir[i_];
mcstage=0;
//--- function call
MNLMCSrch(wcount,network.m_weights,e,g,wdir,wstep,mcinfo,mcnfev,work,mcstate,mcstage);
//--- cycle
while(mcstage!=0)
{
//--- function call
CMLPBase::MLPGradNBatch(network,xy,npoints,e,g);
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=network.m_weights[i_]*network.m_weights[i_];
//--- change value
e=e+0.5*decay*v;
for(i_=0;i_<=wcount-1;i_++)
g[i_]=g[i_]+decay*network.m_weights[i_];
rep.m_ngrad=rep.m_ngrad+1;
//--- function call
MNLMCSrch(wcount,network.m_weights,e,g,wdir,wstep,mcinfo,mcnfev,work,mcstate,mcstage);
}
}
//--- Second stage: use Hessian when we are close to the minimum
while(true)
{
//--- Calculate and update E/G/H
CMLPBase::MLPHessianNBatch(network,xy,npoints,e,g,h);
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=network.m_weights[i_]*network.m_weights[i_];
//--- change value
e=e+0.5*decay*v;
for(i_=0;i_<=wcount-1;i_++)
g[i_]=g[i_]+decay*network.m_weights[i_];
for(k=0;k<=wcount-1;k++)
h[k].Set(k,h[k][k]+decay);
rep.m_nhess=rep.m_nhess+1;
//--- Select step direction
//--- NOTE: it is important to use lower-triangle Cholesky
//--- factorization since it is much faster than higher-triangle version.
spd=CTrFac::SPDMatrixCholesky(h,wcount,false);
//--- function call
CDenseSolver::SPDMatrixCholeskySolve(h,wcount,false,g,solverinfo,solverrep,wdir);
spd=solverinfo>0;
//--- check
if(spd)
{
//--- H is positive definite.
//--- Step in Newton direction.
for(i_=0;i_<=wcount-1;i_++)
wdir[i_]=-1*wdir[i_];
spd=true;
}
else
{
//--- H is indefinite.
//--- Step in gradient direction.
for(i_=0;i_<=wcount-1;i_++)
wdir[i_]=-g[i_];
spd=false;
}
//--- Optimize in WDir direction
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=wdir[i_]*wdir[i_];
//--- change values
wstep=MathSqrt(v);
v=1/MathSqrt(v);
for(i_=0;i_<=wcount-1;i_++)
wdir[i_]=v*wdir[i_];
mcstage=0;
//--- function call
MNLMCSrch(wcount,network.m_weights,e,g,wdir,wstep,mcinfo,mcnfev,work,mcstate,mcstage);
//--- cycle
while(mcstage!=0)
{
//--- function call
CMLPBase::MLPGradNBatch(network,xy,npoints,e,g);
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=network.m_weights[i_]*network.m_weights[i_];
//--- change value
e=e+0.5*decay*v;
for(i_=0;i_<=wcount-1;i_++)
g[i_]=g[i_]+decay*network.m_weights[i_];
rep.m_ngrad=rep.m_ngrad+1;
//--- function call
MNLMCSrch(wcount,network.m_weights,e,g,wdir,wstep,mcinfo,mcnfev,work,mcstate,mcstage);
}
//--- check
if(spd && ((mcinfo==2 || mcinfo==4) || mcinfo==6))
break;
}
//--- Convert from NN format to MNL format
i1_=-offs;
for(i_=offs;i_<=offs+wcount-1;i_++)
lm.m_w[i_]=network.m_weights[i_+i1_];
for(k=0;k<=nvars-1;k++)
{
for(i=0;i<=nclasses-2;i++)
{
s=network.m_columnsigmas[k];
//--- check
if(s==0.0)
s=1;
//--- change values
j=offs+(nvars+1)*i;
v=lm.m_w[j+k];
lm.m_w[j+k]=v/s;
lm.m_w[j+nvars]=lm.m_w[j+nvars]+v*network.m_columnmeans[k]/s;
}
}
//--- calculation
for(k=0;k<=nclasses-2;k++)
lm.m_w[offs+(nvars+1)*k+nvars]=-lm.m_w[offs+(nvars+1)*k+nvars];
}
//+------------------------------------------------------------------+
//| Procesing |
//| INPUT PARAMETERS: |
//| LM - logit model, passed by non-constant reference |
//| (some fields of structure are used as temporaries|
//| when calculating model output). |
//| X - input vector, array[0..NVars-1]. |
//| Y - (possibly) preallocated buffer; if size of Y is |
//| less than NClasses, it will be reallocated.If it |
//| is large enough, it is NOT reallocated, so we |
//| can save some time on reallocation. |
//| OUTPUT PARAMETERS: |
//| Y - result, array[0..NClasses-1] |
//| Vector of posterior probabilities for |
//| classification task. |
//+------------------------------------------------------------------+
static void CLogit::MNLProcess(CLogitModel &lm,double &x[],double &y[])
{
//--- create variables
int nvars=0;
int nclasses=0;
int offs=0;
int i=0;
int i1=0;
double s=0;
//--- check
if(!CAp::Assert(lm.m_w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
return;
//--- initialization
nvars=(int)MathRound(lm.m_w[2]);
nclasses=(int)MathRound(lm.m_w[3]);
offs=(int)MathRound(lm.m_w[4]);
//--- function call
MNLIExp(lm.m_w,x);
s=0;
//--- calculation
i1=offs+(nvars+1)*(nclasses-1);
for(i=i1;i<=i1+nclasses-1;i++)
s=s+lm.m_w[i];
//--- check
if(CAp::Len(y)<nclasses)
ArrayResizeAL(y,nclasses);
//--- change values
for(i=0;i<=nclasses-1;i++)
y[i]=lm.m_w[i1+i]/s;
}
//+------------------------------------------------------------------+
//| 'interactive' variant of MNLProcess for languages like Python |
//| which support constructs like "Y=MNLProcess(LM,X)" and |
//| interactive mode of the interpreter |
//| This function allocates new array on each call, so it is |
//| significantly slower than its 'non-interactive' counterpart, |
//| but it is more convenient when you call it from command line. |
//+------------------------------------------------------------------+
static void CLogit::MNLProcessI(CLogitModel &lm,double &x[],double &y[])
{
//--- function call
MNLProcess(lm,x,y);
}
//+------------------------------------------------------------------+
//| Unpacks coefficients of logit model. Logit model have form: |
//| P(class=i) = S(i) / (S(0) + S(1) + ... +S(M-1)) |
//| S(i) = Exp(A[i,0]*X[0] + ... + A[i,N-1]*X[N-1] + A[i,N]), |
//| when i<M-1 |
//| S(M-1) = 1 |
//| INPUT PARAMETERS: |
//| LM - logit model in ALGLIB format |
//| OUTPUT PARAMETERS: |
//| V - coefficients, array[0..NClasses-2,0..NVars] |
//| NVars - number of independent variables |
//| NClasses - number of classes |
//+------------------------------------------------------------------+
static void CLogit::MNLUnpack(CLogitModel &lm,CMatrixDouble &a,int &nvars,
int &nclasses)
{
//--- create variables
int offs=0;
int i=0;
int i_=0;
int i1_=0;
//--- initialization
nvars=0;
nclasses=0;
//--- check
if(!CAp::Assert(lm.m_w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
return;
//--- initialization
nvars=(int)MathRound(lm.m_w[2]);
nclasses=(int)MathRound(lm.m_w[3]);
offs=(int)MathRound(lm.m_w[4]);
//--- allocation
a.Resize(nclasses-1,nvars+1);
//--- calculation
for(i=0;i<=nclasses-2;i++)
{
i1_=offs+i*(nvars+1);
for(i_=0;i_<=nvars;i_++)
a[i].Set(i_,lm.m_w[i_+i1_]);
}
}
//+------------------------------------------------------------------+
//| "Packs" coefficients and creates logit model in ALGLIB format |
//| (MNLUnpack reversed). |
//| INPUT PARAMETERS: |
//| A - model (see MNLUnpack) |
//| NVars - number of independent variables |
//| NClasses - number of classes |
//| OUTPUT PARAMETERS: |
//| LM - logit model. |
//+------------------------------------------------------------------+
static void CLogit::MNLPack(CMatrixDouble &a,const int nvars,const int nclasses,
CLogitModel &lm)
{
//--- create variables
int offs=0;
int i=0;
int wdim=0;
int ssize=0;
int i_=0;
int i1_=0;
//--- initialization
wdim=(nvars+1)*(nclasses-1);
offs=5;
ssize=5+(nvars+1)*(nclasses-1)+nclasses;
//--- allocation
ArrayResizeAL(lm.m_w,ssize);
//--- initialization
lm.m_w[0]=ssize;
lm.m_w[1]=m_logitvnum;
lm.m_w[2]=nvars;
lm.m_w[3]=nclasses;
lm.m_w[4]=offs;
//--- calculation
for(i=0;i<=nclasses-2;i++)
{
i1_=-(offs+i*(nvars+1));
for(i_=offs+i*(nvars+1);i_<=offs+i*(nvars+1)+nvars;i_++)
lm.m_w[i_]=a[i][i_+i1_];
}
}
//+------------------------------------------------------------------+
//| Copying of LogitModel strucure |
//| INPUT PARAMETERS: |
//| LM1 - original |
//| OUTPUT PARAMETERS: |
//| LM2 - copy |
//+------------------------------------------------------------------+
static void CLogit::MNLCopy(CLogitModel &lm1,CLogitModel &lm2)
{
//--- create variables
int k=0;
int i_=0;
//--- initialization
k=(int)MathRound(lm1.m_w[0]);
//--- allocation
ArrayResizeAL(lm2.m_w,k);
//--- copy
for(i_=0;i_<=k-1;i_++)
lm2.m_w[i_]=lm1.m_w[i_];
}
//+------------------------------------------------------------------+
//| Average cross-entropy (in bits per element) on the test set |
//| INPUT PARAMETERS: |
//| LM - logit model |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| CrossEntropy/(NPoints*ln(2)). |
//+------------------------------------------------------------------+
static double CLogit::MNLAvgCE(CLogitModel &lm,CMatrixDouble &xy,const int npoints)
{
//--- create variables
double result=0;
int nvars=0;
int nclasses=0;
int i=0;
int i_=0;
//--- creating arrays
double workx[];
double worky[];
//--- check
if(!CAp::Assert(lm.m_w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
return(EMPTY_VALUE);
//--- initialization
nvars=(int)MathRound(lm.m_w[2]);
nclasses=(int)MathRound(lm.m_w[3]);
//--- allocation
ArrayResizeAL(workx,nvars);
ArrayResizeAL(worky,nclasses);
//--- calculation
for(i=0;i<=npoints-1;i++)
{
//--- check
if(!CAp::Assert((int)MathRound(xy[i][nvars])>=0 &&(int)MathRound(xy[i][nvars])<nclasses,__FUNCTION__+": incorrect class number!"))
return(EMPTY_VALUE);
//--- Process
for(i_=0;i_<=nvars-1;i_++)
workx[i_]=xy[i][i_];
//--- function call
MNLProcess(lm,workx,worky);
//--- check
if(worky[(int)MathRound(xy[i][nvars])]>0.0)
result=result-MathLog(worky[(int)MathRound(xy[i][nvars])]);
else
result=result-MathLog(CMath::m_minrealnumber);
}
//--- return result
return(result/(npoints*MathLog(2)));
}
//+------------------------------------------------------------------+
//| Relative classification error on the test set |
//| INPUT PARAMETERS: |
//| LM - logit model |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| percent of incorrectly classified cases. |
//+------------------------------------------------------------------+
static double CLogit::MNLRelClsError(CLogitModel &lm,CMatrixDouble &xy,
const int npoints)
{
//--- return result
return((double)MNLClsError(lm,xy,npoints)/(double)npoints);
}
//+------------------------------------------------------------------+
//| RMS error on the test set |
//| INPUT PARAMETERS: |
//| LM - logit model |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| root mean square error (error when estimating posterior |
//| probabilities). |
//+------------------------------------------------------------------+
static double CLogit::MNLRMSError(CLogitModel &lm,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double relcls=0;
double avgce=0;
double rms=0;
double avg=0;
double avgrel=0;
//--- check
if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_logitvnum,__FUNCTION__+": Incorrect MNL version!"))
return(EMPTY_VALUE);
//--- function call
MNLAllErrors(lm,xy,npoints,relcls,avgce,rms,avg,avgrel);
//--- return result
return(rms);
}
//+------------------------------------------------------------------+
//| Average error on the test set |
//| INPUT PARAMETERS: |
//| LM - logit model |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| average error (error when estimating posterior |
//| probabilities). |
//+------------------------------------------------------------------+
static double CLogit::MNLAvgError(CLogitModel &lm,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double relcls=0;
double avgce=0;
double rms=0;
double avg=0;
double avgrel=0;
//--- check
if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_logitvnum,__FUNCTION__+": Incorrect MNL version!"))
return(EMPTY_VALUE);
//--- function call
MNLAllErrors(lm,xy,npoints,relcls,avgce,rms,avg,avgrel);
//--- return result
return(avg);
}
//+------------------------------------------------------------------+
//| Average relative error on the test set |
//| INPUT PARAMETERS: |
//| LM - logit model |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| average relative error (error when estimating posterior |
//| probabilities). |
//+------------------------------------------------------------------+
static double CLogit::MNLAvgRelError(CLogitModel &lm,CMatrixDouble &xy,
const int ssize)
{
//--- create variables
double relcls=0;
double avgce=0;
double rms=0;
double avg=0;
double avgrel=0;
//--- check
if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_logitvnum,__FUNCTION__+": Incorrect MNL version!"))
return(EMPTY_VALUE);
//--- function call
MNLAllErrors(lm,xy,ssize,relcls,avgce,rms,avg,avgrel);
//--- return result
return(avgrel);
}
//+------------------------------------------------------------------+
//| Classification error on test set = MNLRelClsError*NPoints |
//+------------------------------------------------------------------+
static int CLogit::MNLClsError(CLogitModel &lm,CMatrixDouble &xy,const int npoints)
{
//--- create variables
int result=0;
int nvars=0;
int nclasses=0;
int i=0;
int j=0;
int nmax=0;
int i_=0;
//--- creating arrays
double workx[];
double worky[];
//--- check
if(!CAp::Assert(lm.m_w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
return(-1);
//--- initialization
nvars=(int)MathRound(lm.m_w[2]);
nclasses=(int)MathRound(lm.m_w[3]);
//--- allocation
ArrayResizeAL(workx,nvars);
ArrayResizeAL(worky,nclasses);
//--- calculation
for(i=0;i<=npoints-1;i++)
{
//--- Process
for(i_=0;i_<=nvars-1;i_++)
workx[i_]=xy[i][i_];
//--- function call
MNLProcess(lm,workx,worky);
//--- Logit version of the answer
nmax=0;
for(j=0;j<=nclasses-1;j++)
{
//--- check
if(worky[j]>worky[nmax])
nmax=j;
}
//--- compare
if(nmax!=(int)MathRound(xy[i][nvars]))
result=result+1;
}
//--- return result
return(result);
}
//+------------------------------------------------------------------+
//| Internal subroutine. Places exponents of the anti-overflow |
//| shifted internal linear outputs into the service part of the W |
//| array. |
//+------------------------------------------------------------------+
static void CLogit::MNLIExp(double &w[],double &x[])
{
//--- create variables
int nvars=0;
int nclasses=0;
int offs=0;
int i=0;
int i1=0;
double v=0;
double mx=0;
int i_=0;
int i1_=0;
//--- check
if(!CAp::Assert(w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
return;
//--- initialization
nvars=(int)MathRound(w[2]);
nclasses=(int)MathRound(w[3]);
offs=(int)MathRound(w[4]);
//--- calculation
i1=offs+(nvars+1)*(nclasses-1);
for(i=0;i<=nclasses-2;i++)
{
//--- change values
i1_=-(offs+i*(nvars+1));
v=0.0;
for(i_=offs+i*(nvars+1);i_<=offs+i*(nvars+1)+nvars-1;i_++)
v+=w[i_]*x[i_+i1_];
w[i1+i]=v+w[offs+i*(nvars+1)+nvars];
}
//--- change values
w[i1+nclasses-1]=0;
mx=0;
//--- calculation
for(i=i1;i<=i1+nclasses-1;i++)
mx=MathMax(mx,w[i]);
for(i=i1;i<=i1+nclasses-1;i++)
w[i]=MathExp(w[i]-mx);
}
//+------------------------------------------------------------------+
//| Calculation of all types of errors |
//+------------------------------------------------------------------+
static void CLogit::MNLAllErrors(CLogitModel &lm,CMatrixDouble &xy,
const int npoints,double &relcls,
double &avgce,double &rms,double &avg,
double &avgrel)
{
//--- create variables
int nvars=0;
int nclasses=0;
int i=0;
int i_=0;
//--- creating arrays
double buf[];
double workx[];
double y[];
double dy[];
//--- initialization
relcls=0;
avgce=0;
rms=0;
avg=0;
avgrel=0;
//--- check
if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_logitvnum,__FUNCTION__+": Incorrect MNL version!"))
return;
//--- initialization
nvars=(int)MathRound(lm.m_w[2]);
nclasses=(int)MathRound(lm.m_w[3]);
//--- allocation
ArrayResizeAL(workx,nvars);
ArrayResizeAL(y,nclasses);
ArrayResizeAL(dy,1);
//--- function call
CBdSS::DSErrAllocate(nclasses,buf);
for(i=0;i<=npoints-1;i++)
{
for(i_=0;i_<=nvars-1;i_++)
workx[i_]=xy[i][i_];
//--- function call
MNLProcess(lm,workx,y);
//--- change value
dy[0]=xy[i][nvars];
//--- function call
CBdSS::DSErrAccumulate(buf,y,dy);
}
//--- function call
CBdSS::DSErrFinish(buf);
//--- change values
relcls=buf[0];
avgce=buf[1];
rms=buf[2];
avg=buf[3];
avgrel=buf[4];
}
//+------------------------------------------------------------------+
//| The purpose of mcsrch is to find a step which satisfies a |
//| sufficient decrease condition and a curvature condition. |
//| At each stage the subroutine updates an interval of uncertainty |
//| with endpoints stx and sty. The interval of uncertainty is |
//| initially chosen so that it contains a minimizer of the modified |
//| function |
//| f(x+stp*s) - f(x) - ftol*stp*(gradf(x)'s). |
//| If a step is obtained for which the modified function has a |
//| nonpositive function value and nonnegative derivative, then the |
//| interval of uncertainty is chosen so that it contains a minimizer|
//| of f(x+stp*s). |
//| The algorithm is designed to find a step which satisfies the |
//| sufficient decrease condition |
//| f(x+stp*s) .le. f(x) + ftol*stp*(gradf(x)'s), |
//| and the curvature condition |
//| abs(gradf(x+stp*s)'s)) .le. gtol*abs(gradf(x)'s). |
//| If ftol is less than gtol and if, for example, the function is |
//| bounded below, then there is always a step which satisfies both |
//| conditions. If no step can be found which satisfies both |
//| conditions, then the algorithm usually stops when rounding |
//| errors prevent further progress. In this case stp only satisfies |
//| the sufficient decrease condition. |
//| Parameters descriprion |
//| N is a positive integer input variable set to the number of |
//| variables. |
//| X is an array of length n. on input it must contain the base |
//| point for the line search. on output it contains x+stp*s. |
//| F is a variable. on input it must contain the value of f at x. On|
//| output it contains the value of f at x + stp*s. |
//| G is an array of length n. on input it must contain the gradient |
//| of f at x. On output it contains the gradient of f at x + stp*s. |
//| s is an input array of length n which specifies the search |
//| direction. |
//| Stp is a nonnegative variable. on input stp contains an initial|
//| estimate of a satisfactory step. on output stp contains the final|
//| estimate. |
//| Ftol and gtol are nonnegative input variables. termination occurs|
//| when the sufficient decrease condition and the directional |
//| derivative condition are satisfied. |
//| Xtol is a nonnegative input variable. termination occurs when the|
//| relative width of the interval of uncertainty is at most xtol. |
//| Stpmin and stpmax are nonnegative input variables which specify |
//| lower and upper bounds for the step. |
//| Maxfev is a positive integer input variable. termination occurs |
//| when the number of calls to fcn is at least maxfev by the end of |
//| an iteration. |
//| Info is an integer output variable set as follows: |
//| info = 0 improper input parameters. |
//| info = 1 the sufficient decrease condition and the |
//| directional derivative condition hold. |
//| info = 2 relative width of the interval of uncertainty |
//| is at most xtol. |
//| info = 3 number of calls to fcn has reached maxfev. |
//| info = 4 the step is at the lower bound stpmin. |
//| info = 5 the step is at the upper bound stpmax. |
//| info = 6 rounding errors prevent further progress. |
//| there may not be a step which satisfies the |
//| sufficient decrease and curvature conditions. |
//| tolerances may be too small. |
//| Nfev is an integer output variable set to the number of calls to |
//| fcn. |
//| wa is a work array of length n. |
//| argonne national laboratory. minpack project. june 1983 |
//| Jorge J. More', David J. Thuente |
//+------------------------------------------------------------------+
static void CLogit::MNLMCSrch(const int n,double &x[],double &f,double &g[],
double &s[],double &stp,int &info,int &nfev,
double &wa[],CLogitMCState &state,int &stage)
{
//--- create variables
double v=0;
double p5=0;
double p66=0;
double zero=0;
int i_=0;
//--- init
p5=0.5;
p66=0.66;
state.m_xtrapf=4.0;
zero=0;
//--- Main cycle
while(true)
{
//--- check
if(stage==0)
{
//--- NEXT
stage=2;
continue;
}
//--- check
if(stage==2)
{
state.m_infoc=1;
info=0;
//--- CHECK THE INPUT PARAMETERS FOR ERRORS.
if(n<=0 || stp<=0.0 || m_ftol<0.0 || m_gtol<zero || m_xtol<zero || m_stpmin<zero || m_stpmax<m_stpmin || m_maxfev<=0)
{
stage=0;
return;
}
//--- compute the initial gradient in the search direction
//--- and check that s is a descent direction.
v=0.0;
for(i_=0;i_<=n-1;i_++)
v+=g[i_]*s[i_];
state.m_dginit=v;
//--- check
if(state.m_dginit>=0.0)
{
stage=0;
return;
}
//--- initialize local variables.
state.m_brackt=false;
state.m_stage1=true;
nfev=0;
state.m_finit=f;
state.m_dgtest=m_ftol*state.m_dginit;
state.m_width=m_stpmax-m_stpmin;
state.m_width1=state.m_width/p5;
for(i_=0;i_<=n-1;i_++)
wa[i_]=x[i_];
//--- the variables stx,fx,dgx contain the values of the step,
//--- function,and directional derivative at the best step.
//--- the variables sty,fy,dgy contain the value of the step,
//--- function,and derivative at the other endpoint of
//--- the interval of uncertainty.
//--- the variables stp,f,dg contain the values of the step,
//--- function,and derivative at the current step.
state.m_stx=0;
state.m_fx=state.m_finit;
state.m_dgx=state.m_dginit;
state.m_sty=0;
state.m_fy=state.m_finit;
state.m_dgy=state.m_dginit;
//--- NEXT
stage=3;
continue;
}
//--- check
if(stage==3)
{
//--- start of iteration.
//--- set the minimum and maximum steps to correspond
//--- to the present interval of uncertainty.
if(state.m_brackt)
{
//--- check
if(state.m_stx<state.m_sty)
{
state.m_stmin=state.m_stx;
state.m_stmax=state.m_sty;
}
else
{
state.m_stmin=state.m_sty;
state.m_stmax=state.m_stx;
}
}
else
{
state.m_stmin=state.m_stx;
state.m_stmax=stp+state.m_xtrapf*(stp-state.m_stx);
}
//--- force the step to be within the bounds stpmax and stpmin.
if(stp>m_stpmax)
stp=m_stpmax;
//--- check
if(stp<m_stpmin)
stp=m_stpmin;
//--- if an unusual termination is to occur then let
//--- stp be the lowest point obtained so far.
if((((state.m_brackt && (stp<=state.m_stmin || stp>=state.m_stmax)) || nfev>=m_maxfev-1) || state.m_infoc==0) || (state.m_brackt && state.m_stmax-state.m_stmin<=m_xtol*state.m_stmax))
stp=state.m_stx;
//--- evaluate the function and gradient at stp
//--- and compute the directional derivative.
for(i_=0;i_<=n-1;i_++)
x[i_]=wa[i_];
for(i_=0;i_<=n-1;i_++)
x[i_]=x[i_]+stp*s[i_];
//--- next
stage=4;
return;
}
//--- check
if(stage==4)
{
info=0;
nfev=nfev+1;
v=0.0;
//--- calculation
for(i_=0;i_<=n-1;i_++)
v+=g[i_]*s[i_];
state.m_dg=v;
state.m_ftest1=state.m_finit+stp*state.m_dgtest;
//--- test for convergence.
if((state.m_brackt && (stp<=state.m_stmin || stp>=state.m_stmax)) || state.m_infoc==0)
info=6;
//--- check
if((stp==m_stpmax &&f<=state.m_ftest1) &&state.m_dg<=state.m_dgtest)
info=5;
//--- check
if(stp==m_stpmin && (f>state.m_ftest1 || state.m_dg>=state.m_dgtest))
info=4;
//--- check
if(nfev>=m_maxfev)
info=3;
//--- check
if(state.m_brackt && state.m_stmax-state.m_stmin<=m_xtol*state.m_stmax)
info=2;
//--- check
if(f<=state.m_ftest1 && MathAbs(state.m_dg)<=-(m_gtol*state.m_dginit))
info=1;
//--- check for termination.
if(info!=0)
{
stage=0;
return;
}
//--- in the first stage we seek a step for which the modified
//--- function has a nonpositive value and nonnegative derivative.
if((state.m_stage1 && f<=state.m_ftest1) && state.m_dg>=MathMin(m_ftol,m_gtol)*state.m_dginit)
state.m_stage1=false;
//--- a modified function is used to predict the step only if
//--- we have not obtained a step for which the modified
//--- function has a nonpositive function value and nonnegative
//--- derivative,and if a lower function value has been
//--- obtained but the decrease is not sufficient.
if((state.m_stage1 && f<=state.m_fx) && f>state.m_ftest1)
{
//--- define the modified function and derivative values.
state.m_fm=f-stp*state.m_dgtest;
state.m_fxm=state.m_fx-state.m_stx*state.m_dgtest;
state.m_fym=state.m_fy-state.m_sty*state.m_dgtest;
state.m_dgm=state.m_dg-state.m_dgtest;
state.m_dgxm=state.m_dgx-state.m_dgtest;
state.m_dgym=state.m_dgy-state.m_dgtest;
//--- call cstep to update the interval of uncertainty
//--- and to compute the new step.
MNLMCStep(state.m_stx,state.m_fxm,state.m_dgxm,state.m_sty,state.m_fym,state.m_dgym,stp,state.m_fm,state.m_dgm,state.m_brackt,state.m_stmin,state.m_stmax,state.m_infoc);
//--- reset the function and gradient values for f.
state.m_fx=state.m_fxm+state.m_stx*state.m_dgtest;
state.m_fy=state.m_fym+state.m_sty*state.m_dgtest;
state.m_dgx=state.m_dgxm+state.m_dgtest;
state.m_dgy=state.m_dgym+state.m_dgtest;
}
else
{
//--- call mcstep to update the interval of uncertainty
//--- and to compute the new step.
MNLMCStep(state.m_stx,state.m_fx,state.m_dgx,state.m_sty,state.m_fy,state.m_dgy,stp,f,state.m_dg,state.m_brackt,state.m_stmin,state.m_stmax,state.m_infoc);
}
//--- force a sufficient decrease in the size of the
//--- interval of uncertainty.
if(state.m_brackt)
{
//--- check
if(MathAbs(state.m_sty-state.m_stx)>=p66*state.m_width1)
stp=state.m_stx+p5*(state.m_sty-state.m_stx);
state.m_width1=state.m_width;
state.m_width=MathAbs(state.m_sty-state.m_stx);
}
//--- next.
stage=3;
continue;
}
}
}
//+------------------------------------------------------------------+
//| Auxiliary function for MNLMCSrch |
//+------------------------------------------------------------------+
static void CLogit::MNLMCStep(double &stx,double &fx,double &dx,double &sty,
double &fy,double &dy,double &stp,const double fp,
const double dp,bool &brackt,const double stmin,
const double stmax,int &info)
{
//--- create variables
bool bound;
double gamma=0;
double p=0;
double q=0;
double r=0;
double s=0;
double sgnd=0;
double stpc=0;
double stpf=0;
double stpq=0;
double theta=0;
//--- initialization
info=0;
//--- check the input parameters for errors.
if(((brackt && (stp<=MathMin(stx,sty) || stp>=MathMax(stx,sty))) || dx*(stp-stx)>=0.0) || stmax<stmin)
return;
//--- determine if the derivatives have opposite sign.
sgnd=dp*(dx/MathAbs(dx));
//--- first case. a higher function value.
//--- the minimum is bracketed. if the cubic step is closer
//--- to stx than the quadratic step,the cubic step is taken,
//--- else the average of the cubic and quadratic steps is taken.
if(fp>fx)
{
//--- change value
info=1;
bound=true;
theta=3*(fx-fp)/(stp-stx)+dx+dp;
s=MathMax(MathAbs(theta),MathMax(MathAbs(dx),MathAbs(dp)));
gamma=s*MathSqrt(CMath::Sqr(theta/s)-dx/s*(dp/s));
//--- check
if(stp<stx)
gamma=-gamma;
//--- change value
p=gamma-dx+theta;
q=gamma-dx+gamma+dp;
r=p/q;
stpc=stx+r*(stp-stx);
stpq=stx+dx/((fx-fp)/(stp-stx)+dx)/2*(stp-stx);
//--- check
if(MathAbs(stpc-stx)<MathAbs(stpq-stx))
stpf=stpc;
else
stpf=stpc+(stpq-stpc)/2;
brackt=true;
}
else
{
//--- check
if(sgnd<0.0)
{
//--- second case. a lower function value and derivatives of
//--- opposite sign. the minimum is bracketed. if the cubic
//--- step is closer to stx than the quadratic (secant) step,
//--- the cubic step is taken,else the quadratic step is taken.
info=2;
bound=false;
theta=3*(fx-fp)/(stp-stx)+dx+dp;
s=MathMax(MathAbs(theta),MathMax(MathAbs(dx),MathAbs(dp)));
gamma=s*MathSqrt(CMath::Sqr(theta/s)-dx/s*(dp/s));
//--- check
if(stp>stx)
gamma=-gamma;
//--- change values
p=gamma-dp+theta;
q=gamma-dp+gamma+dx;
r=p/q;
stpc=stp+r*(stx-stp);
stpq=stp+dp/(dp-dx)*(stx-stp);
//--- check
if(MathAbs(stpc-stp)>MathAbs(stpq-stp))
stpf=stpc;
else
stpf=stpq;
brackt=true;
}
else
{
//--- check
if(MathAbs(dp)<MathAbs(dx))
{
//--- third case. a lower function value,derivatives of the
//--- same sign,and the magnitude of the derivative decreases.
//--- the cubic step is only used if the cubic tends to infinity
//--- in the direction of the step or if the minimum of the cubic
//--- is beyond stp. otherwise the cubic step is defined to be
//--- either stpmin or stpmax. the quadratic (secant) step is also
//--- computed and if the minimum is bracketed then the the step
//--- closest to stx is taken,else the step farthest away is taken.
info=3;
bound=true;
theta=3*(fx-fp)/(stp-stx)+dx+dp;
s=MathMax(MathAbs(theta),MathMax(MathAbs(dx),MathAbs(dp)));
//--- the case gamma=0 only arises if the cubic does not tend
//--- to infinity in the direction of the step.
gamma=s*MathSqrt(MathMax(0,CMath::Sqr(theta/s)-dx/s*(dp/s)));
//--- check
if(stp>stx)
gamma=-gamma;
p=gamma-dp+theta;
q=gamma+(dx-dp)+gamma;
r=p/q;
//--- check
if(r<0.0 && gamma!=0.0)
stpc=stp+r*(stx-stp);
else
{
//--- check
if(stp>stx)
stpc=stmax;
else
stpc=stmin;
}
stpq=stp+dp/(dp-dx)*(stx-stp);
//--- check
if(brackt)
{
//--- check
if(MathAbs(stp-stpc)<MathAbs(stp-stpq))
stpf=stpc;
else
stpf=stpq;
}
else
{
//--- check
if(MathAbs(stp-stpc)>MathAbs(stp-stpq))
stpf=stpc;
else
stpf=stpq;
}
}
else
{
//--- fourth case. a lower function value,derivatives of the
//--- same sign,and the magnitude of the derivative does
//--- not decrease. if the minimum is not bracketed,the step
//--- is either stpmin or stpmax,else the cubic step is taken.
info=4;
bound=false;
//--- check
if(brackt)
{
//--- change values
theta=3*(fp-fy)/(sty-stp)+dy+dp;
s=MathMax(MathAbs(theta),MathMax(MathAbs(dy),MathAbs(dp)));
gamma=s*MathSqrt(CMath::Sqr(theta/s)-dy/s*(dp/s));
//--- check
if(stp>sty)
gamma=-gamma;
//--- change values
p=gamma-dp+theta;
q=gamma-dp+gamma+dy;
r=p/q;
stpc=stp+r*(sty-stp);
stpf=stpc;
}
else
{
//--- check
if(stp>stx)
stpf=stmax;
else
stpf=stmin;
}
}
}
}
//--- update the interval of uncertainty. this update does not
//--- depend on the new step or the case analysis above.
if(fp>fx)
{
sty=stp;
fy=fp;
dy=dp;
}
else
{
//--- check
if(sgnd<0.0)
{
sty=stx;
fy=fx;
dy=dx;
}
//--- change values
stx=stp;
fx=fp;
dx=dp;
}
//--- compute the new step and safeguard it.
stpf=MathMin(stmax,stpf);
stpf=MathMax(stmin,stpf);
stp=stpf;
//--- check
if(brackt && bound)
{
//--- check
if(sty>stx)
stp=MathMin(stx+0.66*(sty-stx),stp);
else
stp=MathMax(stx+0.66*(sty-stx),stp);
}
}
//+------------------------------------------------------------------+
//| This structure is a MCPD (Markov Chains for Population Data) |
//| solver. You should use ALGLIB functions in order to work with |
//| this object. |
//+------------------------------------------------------------------+
class CMCPDState
{
public:
//--- variables
int m_n;
int m_npairs;
int m_ccnt;
double m_regterm;
int m_repinneriterationscount;
int m_repouteriterationscount;
int m_repnfev;
int m_repterminationtype;
CMinBLEICState m_bs;
CMinBLEICReport m_br;
//--- arrays
int m_states[];
int m_ct[];
double m_pw[];
double m_tmpp[];
double m_effectivew[];
double m_effectivebndl[];
double m_effectivebndu[];
int m_effectivect[];
double m_h[];
//--- matrices
CMatrixDouble m_data;
CMatrixDouble m_ec;
CMatrixDouble m_bndl;
CMatrixDouble m_bndu;
CMatrixDouble m_c;
CMatrixDouble m_priorp;
CMatrixDouble m_effectivec;
CMatrixDouble m_p;
//--- constructor, destructor
CMCPDState(void);
~CMCPDState(void);
//--- copy
void Copy(CMCPDState &obj);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMCPDState::CMCPDState(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMCPDState::~CMCPDState(void)
{
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CMCPDState::Copy(CMCPDState &obj)
{
//--- copy variables
m_n=obj.m_n;
m_npairs=obj.m_npairs;
m_ccnt=obj.m_ccnt;
m_regterm=obj.m_regterm;
m_repinneriterationscount=obj.m_repinneriterationscount;
m_repouteriterationscount=obj.m_repouteriterationscount;
m_repnfev=obj.m_repnfev;
m_repterminationtype=obj.m_repterminationtype;
m_bs.Copy(obj.m_bs);
m_br.Copy(obj.m_br);
//--- copy arrays
ArrayCopy(m_states,obj.m_states);
ArrayCopy(m_ct,obj.m_ct);
ArrayCopy(m_pw,obj.m_pw);
ArrayCopy(m_tmpp,obj.m_tmpp);
ArrayCopy(m_effectivew,obj.m_effectivew);
ArrayCopy(m_effectivebndl,obj.m_effectivebndl);
ArrayCopy(m_effectivebndu,obj.m_effectivebndu);
ArrayCopy(m_effectivect,obj.m_effectivect);
ArrayCopy(m_h,obj.m_h);
//--- copy matrices
m_data=obj.m_data;
m_ec=obj.m_ec;
m_bndl=obj.m_bndl;
m_bndu=obj.m_bndu;
m_c=obj.m_c;
m_priorp=obj.m_priorp;
m_effectivec=obj.m_effectivec;
m_p=obj.m_p;
}
//+------------------------------------------------------------------+
//| This structure is a MCPD (Markov Chains for Population Data) |
//| solver. |
//| You should use ALGLIB functions in order to work with this object|
//+------------------------------------------------------------------+
class CMCPDStateShell
{
private:
CMCPDState m_innerobj;
public:
//--- constructors, destructor
CMCPDStateShell(void);
CMCPDStateShell(CMCPDState &obj);
~CMCPDStateShell(void);
//--- method
CMCPDState *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMCPDStateShell::CMCPDStateShell(void)
{
}
//+------------------------------------------------------------------+
//| Copy constructor |
//+------------------------------------------------------------------+
CMCPDStateShell::CMCPDStateShell(CMCPDState &obj)
{
//--- copy
m_innerobj.Copy(obj);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMCPDStateShell::~CMCPDStateShell(void)
{
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CMCPDState *CMCPDStateShell::GetInnerObj(void)
{
//--- return result
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| This structure is a MCPD training report: |
//| InnerIterationsCount - number of inner iterations of the|
//| underlying optimization algorithm|
//| OuterIterationsCount - number of outer iterations of the|
//| underlying optimization algorithm|
//| NFEV - number of merit function |
//| evaluations |
//| TerminationType - termination type |
//| (same as for MinBLEIC optimizer, |
//| positive values denote success, |
//| negative ones - failure) |
//+------------------------------------------------------------------+
class CMCPDReport
{
public:
//--- variables
int m_inneriterationscount;
int m_outeriterationscount;
int m_nfev;
int m_terminationtype;
//--- constructor, destructor
CMCPDReport(void);
~CMCPDReport(void);
//--- copy
void Copy(CMCPDReport &obj);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMCPDReport::CMCPDReport(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMCPDReport::~CMCPDReport(void)
{
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CMCPDReport::Copy(CMCPDReport &obj)
{
//--- copy variables
m_inneriterationscount=obj.m_inneriterationscount;
m_outeriterationscount=obj.m_outeriterationscount;
m_nfev=obj.m_nfev;
m_terminationtype=obj.m_terminationtype;
}
//+------------------------------------------------------------------+
//| This structure is a MCPD training report: |
//| InnerIterationsCount - number of inner iterations of the|
//| underlying optimization algorithm|
//| OuterIterationsCount - number of outer iterations of the|
//| underlying optimization algorithm|
//| NFEV - number of merit function |
//| evaluations |
//| TerminationType - termination type |
//| (same as for MinBLEIC optimizer, |
//| positive values denote success, |
//| negative ones - failure) |
//+------------------------------------------------------------------+
class CMCPDReportShell
{
private:
CMCPDReport m_innerobj;
public:
//--- constructors, destructor
CMCPDReportShell(void);
CMCPDReportShell(CMCPDReport &obj);
~CMCPDReportShell(void);
//--- methods
int GetInnerIterationsCount(void);
void SetInnerIterationsCount(const int i);
int GetOuterIterationsCount(void);
void SetOuterIterationsCount(const int i);
int GetNFev(void);
void SetNFev(const int i);
int GetTerminationType(void);
void SetTerminationType(const int i);
CMCPDReport *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMCPDReportShell::CMCPDReportShell(void)
{
}
//+------------------------------------------------------------------+
//| Copy constructor |
//+------------------------------------------------------------------+
CMCPDReportShell::CMCPDReportShell(CMCPDReport &obj)
{
//--- copy
m_innerobj.Copy(obj);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMCPDReportShell::~CMCPDReportShell(void)
{
}
//+------------------------------------------------------------------+
//| Returns the value of the variable inneriterationscount |
//+------------------------------------------------------------------+
int CMCPDReportShell::GetInnerIterationsCount(void)
{
//--- return result
return(m_innerobj.m_inneriterationscount);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable inneriterationscount |
//+------------------------------------------------------------------+
void CMCPDReportShell::SetInnerIterationsCount(const int i)
{
//--- change value
m_innerobj.m_inneriterationscount=i;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable outeriterationscount |
//+------------------------------------------------------------------+
int CMCPDReportShell::GetOuterIterationsCount(void)
{
//--- return result
return(m_innerobj.m_outeriterationscount);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable outeriterationscount |
//+------------------------------------------------------------------+
void CMCPDReportShell::SetOuterIterationsCount(const int i)
{
//--- change value
m_innerobj.m_outeriterationscount=i;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable nfev |
//+------------------------------------------------------------------+
int CMCPDReportShell::GetNFev(void)
{
//--- return result
return(m_innerobj.m_nfev);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable nfev |
//+------------------------------------------------------------------+
void CMCPDReportShell::SetNFev(const int i)
{
//--- change value
m_innerobj.m_nfev=i;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable terminationtype |
//+------------------------------------------------------------------+
int CMCPDReportShell::GetTerminationType(void)
{
//--- return result
return(m_innerobj.m_terminationtype);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable terminationtype |
//+------------------------------------------------------------------+
void CMCPDReportShell::SetTerminationType(const int i)
{
//--- change value
m_innerobj.m_terminationtype=i;
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CMCPDReport *CMCPDReportShell::GetInnerObj(void)
{
//--- return result
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Markov chains class |
//+------------------------------------------------------------------+
class CMarkovCPD
{
private:
//--- private method
static void MCPDInit(const int n,const int entrystate,const int exitstate,CMCPDState &s);
public:
//--- constant
static const double m_xtol;
//--- constructor, destructor
CMarkovCPD(void);
~CMarkovCPD(void);
//--- public methods
static void MCPDCreate(const int n,CMCPDState &s);
static void MCPDCreateEntry(const int n,const int entrystate,CMCPDState &s);
static void MCPDCreateExit(const int n,const int exitstate,CMCPDState &s);
static void MCPDCreateEntryExit(const int n,const int entrystate,const int exitstate,CMCPDState &s);
static void MCPDAddTrack(CMCPDState &s,CMatrixDouble &xy,const int k);
static void MCPDSetEC(CMCPDState &s,CMatrixDouble &ec);
static void MCPDAddEC(CMCPDState &s,const int i,const int j,const double c);
static void MCPDSetBC(CMCPDState &s,CMatrixDouble &bndl,CMatrixDouble &bndu);
static void MCPDAddBC(CMCPDState &s,const int i,const int j,double bndl,double bndu);
static void MCPDSetLC(CMCPDState &s,CMatrixDouble &c,int &ct[],const int k);
static void MCPDSetTikhonovRegularizer(CMCPDState &s,const double v);
static void MCPDSetPrior(CMCPDState &s,CMatrixDouble &cpp);
static void MCPDSetPredictionWeights(CMCPDState &s,double &pw[]);
static void MCPDSolve(CMCPDState &s);
static void MCPDResults(CMCPDState &s,CMatrixDouble &p,CMCPDReport &rep);
};
//+------------------------------------------------------------------+
//| Initialize constant |
//+------------------------------------------------------------------+
const double CMarkovCPD::m_xtol=1.0E-8;
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMarkovCPD::CMarkovCPD(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMarkovCPD::~CMarkovCPD(void)
{
}
//+------------------------------------------------------------------+
//| DESCRIPTION: |
//| This function creates MCPD (Markov Chains for Population Data) |
//| solver. |
//| This solver can be used to find transition matrix P for |
//| N-dimensional prediction problem where transition from X[i] to |
//| X[i+1] is modelled as X[i+1] = P*X[i] |
//| where X[i] and X[i+1] are N-dimensional population vectors |
//| (components of each X are non-negative), and P is a N*N |
//| transition matrix (elements of are non-negative, each column |
//| sums to 1.0). |
//| Such models arise when when: |
//| * there is some population of individuals |
//| * individuals can have different states |
//| * individuals can transit from one state to another |
//| * population size is constant, i.e. there is no new individuals |
//| and no one leaves population |
//| * you want to model transitions of individuals from one state |
//| into another |
//| USAGE: |
//| Here we give very brief outline of the MCPD. We strongly |
//| recommend you to read examples in the ALGLIB Reference Manual |
//| and to read ALGLIB User Guide on data analysis which is |
//| available at http://www.alglib.net/dataanalysis/ |
//| 1. User initializes algorithm state with MCPDCreate() call |
//| 2. User adds one or more tracks - sequences of states which |
//| describe evolution of a system being modelled from different |
//| starting conditions |
//| 3. User may add optional boundary, equality and/or linear |
//| constraints on the coefficients of P by calling one of the |
//| following functions: |
//| * MCPDSetEC() to set equality constraints |
//| * MCPDSetBC() to set bound constraints |
//| * MCPDSetLC() to set linear constraints |
//| 4. Optionally, user may set custom weights for prediction errors |
//| (by default, algorithm assigns non-equal, automatically chosen|
//| weights for errors in the prediction of different components |
//| of X). It can be done with a call of |
//| MCPDSetPredictionWeights() function. |
//| 5. User calls MCPDSolve() function which takes algorithm state |
//| and pointer (delegate, etc.) to callback function which |
//| calculates F/G. |
//| 6. User calls MCPDResults() to get solution |
//| INPUT PARAMETERS: |
//| N - problem dimension, N>=1 |
//| OUTPUT PARAMETERS: |
//| State - structure stores algorithm state |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDCreate(const int n,CMCPDState &s)
{
//--- check
if(!CAp::Assert(n>=1,"MCPDCreate: N<1"))
return;
//--- function call
MCPDInit(n,-1,-1,s);
}
//+------------------------------------------------------------------+
//| DESCRIPTION: |
//| This function is a specialized version of MCPDCreate() function, |
//| and we recommend you to read comments for this function for |
//| general information about MCPD solver. |
//| This function creates MCPD (Markov Chains for Population Data) |
//| solver for "Entry-state" model, i.e. model where transition from |
//| X[i] to X[i+1] is modelled as |
//| X[i+1] = P*X[i] |
//| where |
//| X[i] and X[i+1] are N-dimensional state vectors |
//| P is a N*N transition matrix |
//| and one selected component of X[] is called "entry" state and |
//| is treated in a special way: |
//| system state always transits from "entry" state to some |
//| another state |
//| system state can not transit from any state into "entry" |
//| state |
//| Such conditions basically mean that row of P which corresponds to|
//| "entry" state is zero. |
//| Such models arise when: |
//| * there is some population of individuals |
//| * individuals can have different states |
//| * individuals can transit from one state to another |
//| * population size is NOT constant - at every moment of time |
//| there is some (unpredictable) amount of "new" individuals, |
//| which can transit into one of the states at the next turn, but |
//| still no one leaves population |
//| * you want to model transitions of individuals from one state |
//| into another |
//| * but you do NOT want to predict amount of "new" individuals |
//| because it does not depends on individuals already present |
//| (hence system can not transit INTO entry state - it can only |
//| transit FROM it). |
//| This model is discussed in more details in the ALGLIB User Guide |
//| (see http://www.alglib.net/dataanalysis/ for more data). |
//| INPUT PARAMETERS: |
//| N - problem dimension, N>=2 |
//| EntryState- index of entry state, in 0..N-1 |
//| OUTPUT PARAMETERS: |
//| State - structure stores algorithm state |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDCreateEntry(const int n,const int entrystate,
CMCPDState &s)
{
//--- check
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
return;
//--- check
if(!CAp::Assert(entrystate>=0,__FUNCTION__+": EntryState<0"))
return;
//--- check
if(!CAp::Assert(entrystate<n,__FUNCTION__+": EntryState>=N"))
return;
//--- function call
MCPDInit(n,entrystate,-1,s);
}
//+------------------------------------------------------------------+
//| DESCRIPTION: |
//| This function is a specialized version of MCPDCreate() function, |
//| and we recommend you to read comments for this function for |
//| general information about MCPD solver. |
//| This function creates MCPD (Markov Chains for Population Data) |
//| solver for "Exit-state" model, i.e. model where transition from |
//| X[i] to X[i+1] is modelled as |
//| X[i+1] = P*X[i] |
//| where |
//| X[i] and X[i+1] are N-dimensional state vectors |
//| P is a N*N transition matrix |
//| and one selected component of X[] is called "exit" state and |
//| is treated in a special way: |
//| system state can transit from any state into "exit" state |
//| system state can not transit from "exit" state into any other|
//| state transition operator discards "exit" state (makes it |
//| zero at each turn) |
//| Such conditions basically mean that column of P which |
//| corresponds to "exit" state is zero. Multiplication by such P |
//| may decrease sum of vector components. |
//| Such models arise when: |
//| * there is some population of individuals |
//| * individuals can have different states |
//| * individuals can transit from one state to another |
//| * population size is NOT constant - individuals can move into |
//| "exit" state and leave population at the next turn, but there |
//| are no new individuals |
//| * amount of individuals which leave population can be predicted |
//| * you want to model transitions of individuals from one state |
//| into another (including transitions into the "exit" state) |
//| This model is discussed in more details in the ALGLIB User Guide |
//| (see http://www.alglib.net/dataanalysis/ for more data). |
//| INPUT PARAMETERS: |
//| N - problem dimension, N>=2 |
//| ExitState- index of exit state, in 0..N-1 |
//| OUTPUT PARAMETERS: |
//| State - structure stores algorithm state |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDCreateExit(const int n,const int exitstate,
CMCPDState &s)
{
//--- check
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
return;
//--- check
if(!CAp::Assert(exitstate>=0,__FUNCTION__+": ExitState<0"))
return;
//--- check
if(!CAp::Assert(exitstate<n,__FUNCTION__+": ExitState>=N"))
return;
//--- function call
MCPDInit(n,-1,exitstate,s);
}
//+------------------------------------------------------------------+
//| DESCRIPTION: |
//| This function is a specialized version of MCPDCreate() function, |
//| and we recommend you to read comments for this function for |
//| general information about MCPD solver. |
//| This function creates MCPD (Markov Chains for Population Data) |
//| solver for "Entry-Exit-states" model, i.e. model where transition|
//| from X[i] to X[i+1] is modelled as |
//| X[i+1] = P*X[i] |
//| where |
//| X[i] and X[i+1] are N-dimensional state vectors |
//| P is a N*N transition matrix |
//| one selected component of X[] is called "entry" state and is a |
//| treated in special way: |
//| system state always transits from "entry" state to some |
//| another state |
//| system state can not transit from any state into "entry" |
//| state |
//| and another one component of X[] is called "exit" state and is |
//| treated in a special way too: |
//| system state can transit from any state into "exit" state |
//| system state can not transit from "exit" state into any other|
//| state transition operator discards "exit" state (makes it |
//| zero at each turn) |
//| Such conditions basically mean that: |
//| row of P which corresponds to "entry" state is zero |
//| column of P which corresponds to "exit" state is zero |
//| Multiplication by such P may decrease sum of vector components. |
//| Such models arise when: |
//| * there is some population of individuals |
//| * individuals can have different states |
//| * individuals can transit from one state to another |
//| * population size is NOT constant |
//| * at every moment of time there is some (unpredictable) amount |
//| of "new" individuals, which can transit into one of the states |
//| at the next turn |
//| * some individuals can move (predictably) into "exit" state |
//| and leave population at the next turn |
//| * you want to model transitions of individuals from one state |
//| into another, including transitions from the "entry" state and |
//| into the "exit" state. |
//| * but you do NOT want to predict amount of "new" individuals |
//| because it does not depends on individuals already present |
//| (hence system can not transit INTO entry state - it can only |
//| transit FROM it). |
//| This model is discussed in more details in the ALGLIB User |
//| Guide (see http://www.alglib.net/dataanalysis/ for more data). |
//| INPUT PARAMETERS: |
//| N - problem dimension, N>=2 |
//| EntryState- index of entry state, in 0..N-1 |
//| ExitState- index of exit state, in 0..N-1 |
//| OUTPUT PARAMETERS: |
//| State - structure stores algorithm state |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDCreateEntryExit(const int n,const int entrystate,
const int exitstate,CMCPDState &s)
{
//--- check
if(!CAp::Assert(n>=2,__FUNCTION__+": N<2"))
return;
//--- check
if(!CAp::Assert(entrystate>=0,__FUNCTION__+": EntryState<0"))
return;
//--- check
if(!CAp::Assert(entrystate<n,__FUNCTION__+": EntryState>=N"))
return;
//--- check
if(!CAp::Assert(exitstate>=0,__FUNCTION__+": ExitState<0"))
return;
//--- check
if(!CAp::Assert(exitstate<n,__FUNCTION__+": ExitState>=N"))
return;
//--- check
if(!CAp::Assert(entrystate!=exitstate,__FUNCTION__+": EntryState=ExitState"))
return;
//--- function call
MCPDInit(n,entrystate,exitstate,s);
}
//+------------------------------------------------------------------+
//| This function is used to add a track - sequence of system states |
//| at the different moments of its evolution. |
//| You may add one or several tracks to the MCPD solver. In case you|
//| have several tracks, they won't overwrite each other. For |
//| example, if you pass two tracks, A1-A2-A3 (system at t=A+1, t=A+2|
//| and t=A+3) and B1-B2-B3, then solver will try to model |
//| transitions from t=A+1 to t=A+2, t=A+2 to t=A+3, t=B+1 to t=B+2, |
//| t=B+2 to t=B+3. But it WONT mix these two tracks - i.e. it wont |
//| try to model transition from t=A+3 to t=B+1. |
//| INPUT PARAMETERS: |
//| S - solver |
//| XY - track, array[K,N]: |
//| * I-th row is a state at t=I |
//| * elements of XY must be non-negative (exception |
//| will be thrown on negative elements) |
//| K - number of points in a track |
//| * if given, only leading K rows of XY are used |
//| * if not given, automatically determined from |
//| size of XY |
//| NOTES: |
//| 1. Track may contain either proportional or population data: |
//| * with proportional data all rows of XY must sum to 1.0, i.e. |
//| we have proportions instead of absolute population values |
//| * with population data rows of XY contain population counts |
//| and generally do not sum to 1.0 (although they still must be|
//| non-negative) |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDAddTrack(CMCPDState &s,CMatrixDouble &xy,
const int k)
{
//--- create variables
int i=0;
int j=0;
int n=0;
double s0=0;
double s1=0;
//--- initialization
n=s.m_n;
//--- check
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0"))
return;
//--- check
if(!CAp::Assert(CAp::Cols(xy)>=n,__FUNCTION__+": Cols(XY)<N"))
return;
//--- check
if(!CAp::Assert(CAp::Rows(xy)>=k,__FUNCTION__+": Rows(XY)<K"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,k,n),__FUNCTION__+": XY contains infinite or NaN elements"))
return;
for(i=0;i<=k-1;i++)
{
for(j=0;j<=n-1;j++)
{
//--- check
if(!CAp::Assert((double)(xy[i][j])>=0.0,__FUNCTION__+": XY contains negative elements"))
return;
}
}
//--- check
if(k<2)
return;
//--- check
if(CAp::Rows(s.m_data)<s.m_npairs+k-1)
CApServ::RMatrixResize(s.m_data,MathMax(2*CAp::Rows(s.m_data),s.m_npairs+k-1),2*n);
//--- calculation
for(i=0;i<=k-2;i++)
{
s0=0;
s1=0;
for(j=0;j<=n-1;j++)
{
//--- check
if(s.m_states[j]>=0)
s0=s0+xy[i][j];
//--- check
if(s.m_states[j]<=0)
s1=s1+xy[i+1][j];
}
//--- check
if(s0>0.0 && s1>0.0)
{
for(j=0;j<=n-1;j++)
{
//--- check
if(s.m_states[j]>=0)
s.m_data[s.m_npairs].Set(j,xy[i][j]/s0);
else
s.m_data[s.m_npairs].Set(j,0.0);
//--- check
if(s.m_states[j]<=0)
s.m_data[s.m_npairs].Set(n+j,xy[i+1][j]/s1);
else
s.m_data[s.m_npairs].Set(n+j,0.0);
}
//--- change value
s.m_npairs=s.m_npairs+1;
}
}
}
//+------------------------------------------------------------------+
//| This function is used to add equality constraints on the elements|
//| of the transition matrix P. |
//| MCPD solver has four types of constraints which can be placed |
//| on P: |
//| * user-specified equality constraints (optional) |
//| * user-specified bound constraints (optional) |
//| * user-specified general linear constraints (optional) |
//| * basic constraints (always present): |
//| * non-negativity: P[i,j]>=0 |
//| * consistency: every column of P sums to 1.0 |
//| Final constraints which are passed to the underlying optimizer |
//| are calculated as intersection of all present constraints. For |
//| example, you may specify boundary constraint on P[0,0] and |
//| equality one: |
//| 0.1<=P[0,0]<=0.9 |
//| P[0,0]=0.5 |
//| Such combination of constraints will be silently reduced to their|
//| intersection, which is P[0,0]=0.5. |
//| This function can be used to place equality constraints on |
//| arbitrary subset of elements of P. Set of constraints is |
//| specified by EC, which may contain either NAN's or finite numbers|
//| from [0,1]. NAN denotes absence of constraint, finite number |
//| denotes equality constraint on specific element of P. |
//| You can also use MCPDAddEC() function which allows to ADD |
//| equality constraint for one element of P without changing |
//| constraints for other elements. |
//| These functions (MCPDSetEC and MCPDAddEC) interact as follows: |
//| * there is internal matrix of equality constraints which is |
//| stored in the MCPD solver |
//| * MCPDSetEC() replaces this matrix by another one (SET) |
//| * MCPDAddEC() modifies one element of this matrix and leaves |
//| other ones unchanged (ADD) |
//| * thus MCPDAddEC() call preserves all modifications done by |
//| previous calls, while MCPDSetEC() completely discards all |
//| changes done to the equality constraints. |
//| INPUT PARAMETERS: |
//| S - solver |
//| EC - equality constraints, array[N,N]. Elements of EC |
//| can be either NAN's or finite numbers from [0,1].|
//| NAN denotes absence of constraints, while finite |
//| value denotes equality constraint on the |
//| corresponding element of P. |
//| NOTES: |
//| 1. infinite values of EC will lead to exception being thrown. |
//| Values less than 0.0 or greater than 1.0 will lead to error code |
//| being returned after call to MCPDSolve(). |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDSetEC(CMCPDState &s,CMatrixDouble &ec)
{
//--- create variables
int i=0;
int j=0;
int n=0;
//--- initialization
n=s.m_n;
//--- check
if(!CAp::Assert(CAp::Cols(ec)>=n,__FUNCTION__+": Cols(EC)<N"))
return;
//--- check
if(!CAp::Assert(CAp::Rows(ec)>=n,__FUNCTION__+": Rows(EC)<N"))
return;
//--- calculation
for(i=0;i<=n-1;i++)
{
for(j=0;j<=n-1;j++)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(ec[i][j]) || CInfOrNaN::IsNaN(ec[i][j]),"MCPDSetEC: EC containts infinite elements"))
return;
s.m_ec[i].Set(j,ec[i][j]);
}
}
}
//+------------------------------------------------------------------+
//| This function is used to add equality constraints on the elements|
//| of the transition matrix P. |
//| MCPD solver has four types of constraints which can be placed |
//| on P: |
//| * user-specified equality constraints (optional) |
//| * user-specified bound constraints (optional) |
//| * user-specified general linear constraints (optional) |
//| * basic constraints (always present): |
//| * non-negativity: P[i,j]>=0 |
//| * consistency: every column of P sums to 1.0 |
//| Final constraints which are passed to the underlying optimizer |
//| are calculated as intersection of all present constraints. For |
//| example, you may specify boundary constraint on P[0,0] and |
//| equality one: |
//| 0.1<=P[0,0]<=0.9 |
//| P[0,0]=0.5 |
//| Such combination of constraints will be silently reduced to their|
//| intersection, which is P[0,0]=0.5. |
//| This function can be used to ADD equality constraint for one |
//| element of P without changing constraints for other elements. |
//| You can also use MCPDSetEC() function which allows you to specify|
//| arbitrary set of equality constraints in one call. |
//| These functions (MCPDSetEC and MCPDAddEC) interact as follows: |
//| * there is internal matrix of equality constraints which is |
//| stored in the MCPD solver |
//| * MCPDSetEC() replaces this matrix by another one (SET) |
//| * MCPDAddEC() modifies one element of this matrix and leaves |
//| other ones unchanged (ADD) |
//| * thus MCPDAddEC() call preserves all modifications done by |
//| previous calls, while MCPDSetEC() completely discards all |
//| changes done to the equality constraints. |
//| INPUT PARAMETERS: |
//| S - solver |
//| I - row index of element being constrained |
//| J - column index of element being constrained |
//| C - value (constraint for P[I,J]). Can be either NAN |
//| (no constraint) or finite value from [0,1]. |
//| NOTES: |
//| 1. infinite values of C will lead to exception being thrown. |
//| Values less than 0.0 or greater than 1.0 will lead to error code |
//| being returned after call to MCPDSolve(). |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDAddEC(CMCPDState &s,const int i,const int j,
const double c)
{
//--- check
if(!CAp::Assert(i>=0,__FUNCTION__+": I<0"))
return;
//--- check
if(!CAp::Assert(i<s.m_n,__FUNCTION__+": I>=N"))
return;
//--- check
if(!CAp::Assert(j>=0,__FUNCTION__+": J<0"))
return;
//--- check
if(!CAp::Assert(j<s.m_n,__FUNCTION__+": J>=N"))
return;
//--- check
if(!CAp::Assert(CInfOrNaN::IsNaN(c) || CMath::IsFinite(c),"MCPDAddEC: C is not finite number or NAN"))
return;
s.m_ec[i].Set(j,c);
}
//+------------------------------------------------------------------+
//| This function is used to add bound constraints on the elements |
//| of the transition matrix P. |
//| MCPD solver has four types of constraints which can be placed |
//| on P: |
//| * user-specified equality constraints (optional) |
//| * user-specified bound constraints (optional) |
//| * user-specified general linear constraints (optional) |
//| * basic constraints (always present): |
//| * non-negativity: P[i,j]>=0 |
//| * consistency: every column of P sums to 1.0 |
//| Final constraints which are passed to the underlying optimizer |
//| are calculated as intersection of all present constraints. For |
//| example, you may specify boundary constraint on P[0,0] and |
//| equality one: |
//| 0.1<=P[0,0]<=0.9 |
//| P[0,0]=0.5 |
//| Such combination of constraints will be silently reduced to their|
//| intersection, which is P[0,0]=0.5. |
//| This function can be used to place bound constraints on arbitrary|
//| subset of elements of P. Set of constraints is specified by |
//| BndL/BndU matrices, which may contain arbitrary combination of |
//| finite numbers or infinities (like -INF<x<=0.5 or 0.1<=x<+INF). |
//| You can also use MCPDAddBC() function which allows to ADD bound |
//| constraint for one element of P without changing constraints for |
//| other elements. |
//| These functions (MCPDSetBC and MCPDAddBC) interact as follows: |
//| * there is internal matrix of bound constraints which is stored |
//| in the MCPD solver |
//| * MCPDSetBC() replaces this matrix by another one (SET) |
//| * MCPDAddBC() modifies one element of this matrix and leaves |
//| other ones unchanged (ADD) |
//| * thus MCPDAddBC() call preserves all modifications done by |
//| previous calls, while MCPDSetBC() completely discards all |
//| changes done to the equality constraints. |
//| INPUT PARAMETERS: |
//| S - solver |
//| BndL - lower bounds constraints, array[N,N]. Elements of|
//| BndL can be finite numbers or -INF. |
//| BndU - upper bounds constraints, array[N,N]. Elements of|
//| BndU can be finite numbers or +INF. |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDSetBC(CMCPDState &s,CMatrixDouble &bndl,
CMatrixDouble &bndu)
{
//--- create variables
int i=0;
int j=0;
int n=0;
//--- initialization
n=s.m_n;
//--- check
if(!CAp::Assert(CAp::Cols(bndl)>=n,__FUNCTION__+": Cols(BndL)<N"))
return;
//--- check
if(!CAp::Assert(CAp::Rows(bndl)>=n,__FUNCTION__+": Rows(BndL)<N"))
return;
//--- check
if(!CAp::Assert(CAp::Cols(bndu)>=n,__FUNCTION__+": Cols(BndU)<N"))
return;
//--- check
if(!CAp::Assert(CAp::Rows(bndu)>=n,__FUNCTION__+": Rows(BndU)<N"))
return;
//--- calculation
for(i=0;i<=n-1;i++)
{
for(j=0;j<=n-1;j++)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(bndl[i][j]) || CInfOrNaN::IsNegativeInfinity(bndl[i][j]),"MCPDSetBC: BndL containts NAN or +INF"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(bndu[i][j]) || CInfOrNaN::IsPositiveInfinity(bndu[i][j]),"MCPDSetBC: BndU containts NAN or -INF"))
return;
//--- change values
s.m_bndl[i].Set(j,bndl[i][j]);
s.m_bndu[i].Set(j,bndu[i][j]);
}
}
}
//+------------------------------------------------------------------+
//| This function is used to add bound constraints on the elements |
//| of the transition matrix P. |
//| MCPD solver has four types of constraints which can be placed |
//| on P: |
//| * user-specified equality constraints (optional) |
//| * user-specified bound constraints (optional) |
//| * user-specified general linear constraints (optional) |
//| * basic constraints (always present): |
//| * non-negativity: P[i,j]>=0 |
//| * consistency: every column of P sums to 1.0 |
//| Final constraints which are passed to the underlying optimizer |
//| are calculated as intersection of all present constraints. For |
//| example, you may specify boundary constraint on P[0,0] and |
//| equality one: |
//| 0.1<=P[0,0]<=0.9 |
//| P[0,0]=0.5 |
//| Such combination of constraints will be silently reduced to their|
//| intersection, which is P[0,0]=0.5. |
//| This function can be used to ADD bound constraint for one element|
//| of P without changing constraints for other elements. |
//| You can also use MCPDSetBC() function which allows to place bound|
//| constraints on arbitrary subset of elements of P. Set of |
//| constraints is specified by BndL/BndU matrices, which may |
//| contain arbitrary combination of finite numbers or infinities |
//| (like -INF<x<=0.5 or 0.1<=x<+INF). |
//| These functions (MCPDSetBC and MCPDAddBC) interact as follows: |
//| * there is internal matrix of bound constraints which is stored |
//| in the MCPD solver |
//| * MCPDSetBC() replaces this matrix by another one (SET) |
//| * MCPDAddBC() modifies one element of this matrix and leaves |
//| other ones unchanged (ADD) |
//| * thus MCPDAddBC() call preserves all modifications done by |
//| previous calls, while MCPDSetBC() completely discards all |
//| changes done to the equality constraints. |
//| INPUT PARAMETERS: |
//| S - solver |
//| I - row index of element being constrained |
//| J - column index of element being constrained |
//| BndL - lower bound |
//| BndU - upper bound |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDAddBC(CMCPDState &s,const int i,const int j,
double bndl,double bndu)
{
//--- check
if(!CAp::Assert(i>=0,__FUNCTION__+": I<0"))
return;
//--- check
if(!CAp::Assert(i<s.m_n,__FUNCTION__+": I>=N"))
return;
//--- check
if(!CAp::Assert(j>=0,__FUNCTION__+": J<0"))
return;
//--- check
if(!CAp::Assert(j<s.m_n,__FUNCTION__+": J>=N"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(bndl) || CInfOrNaN::IsNegativeInfinity(bndl),"MCPDAddBC: BndL is NAN or +INF"))
return;
//--- check
if(!CAp::Assert(CMath::IsFinite(bndu) || CInfOrNaN::IsPositiveInfinity(bndu),"MCPDAddBC: BndU is NAN or -INF"))
return;
//--- change values
s.m_bndl[i].Set(j,bndl);
s.m_bndu[i].Set(j,bndu);
}
//+------------------------------------------------------------------+
//| This function is used to set linear equality/inequality |
//| constraints on the elements of the transition matrix P. |
//| This function can be used to set one or several general linear |
//| constraints on the elements of P. Two types of constraints are |
//| supported: |
//| * equality constraints |
//| * inequality constraints (both less-or-equal and |
//| greater-or-equal) |
//| Coefficients of constraints are specified by matrix C (one of the|
//| parameters). One row of C corresponds to one constraint. |
//| Because transition matrix P has N*N elements, we need N*N columns|
//| to store all coefficients (they are stored row by row), and |
//| one more column to store right part - hence C has N*N+1 columns. |
//| Constraint kind is stored in the CT array. |
//| Thus, I-th linear constraint is |
//| P[0,0]*C[I,0] + P[0,1]*C[I,1] + .. + P[0,N-1]*C[I,N-1] + |
//| + P[1,0]*C[I,N] + P[1,1]*C[I,N+1] + ... + |
//| + P[N-1,N-1]*C[I,N*N-1] ?=? C[I,N*N] |
//| where ?=? can be either "=" (CT[i]=0), "<=" (CT[i]<0) or ">=" |
//| (CT[i]>0). |
//| Your constraint may involve only some subset of P (less than N*N |
//| elements). |
//| For example it can be something like |
//| P[0,0] + P[0,1] = 0.5 |
//| In this case you still should pass matrix with N*N+1 columns, |
//| but all its elements (except for C[0,0], C[0,1] and C[0,N*N-1]) |
//| will be zero. |
//| INPUT PARAMETERS: |
//| S - solver |
//| C - array[K,N*N+1] - coefficients of constraints |
//| (see above for complete description) |
//| CT - array[K] - constraint types |
//| (see above for complete description) |
//| K - number of equality/inequality constraints, K>=0: |
//| * if given, only leading K elements of C/CT are |
//| used |
//| * if not given, automatically determined from |
//| sizes of C/CT |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDSetLC(CMCPDState &s,CMatrixDouble &c,int &ct[],
const int k)
{
//--- create variables
int i=0;
int j=0;
int n=0;
//--- initialization
n=s.m_n;
//--- check
if(!CAp::Assert(CAp::Cols(c)>=n*n+1,__FUNCTION__+": Cols(C)<N*N+1"))
return;
//--- check
if(!CAp::Assert(CAp::Rows(c)>=k,__FUNCTION__+": Rows(C)<K"))
return;
//--- check
if(!CAp::Assert(CAp::Len(ct)>=k,__FUNCTION__+": Len(CT)<K"))
return;
//--- check
if(!CAp::Assert(CApServ::IsFiniteMatrix(c,k,n*n+1),__FUNCTION__+": C contains infinite or NaN values!"))
return;
//--- function call
CApServ::RMatrixSetLengthAtLeast(s.m_c,k,n*n+1);
//--- function call
CApServ::IVectorSetLengthAtLeast(s.m_ct,k);
//--- calculation
for(i=0;i<=k-1;i++)
{
for(j=0;j<=n*n;j++)
s.m_c[i].Set(j,c[i][j]);
s.m_ct[i]=ct[i];
}
s.m_ccnt=k;
}
//+------------------------------------------------------------------+
//| This function allows to tune amount of Tikhonov regularization |
//| being applied to your problem. |
//| By default, regularizing term is equal to r*||P-prior_P||^2, |
//| where r is a small non-zero value, P is transition matrix, |
//| prior_P is identity matrix, ||X||^2 is a sum of squared elements |
//| of X. |
//| This function allows you to change coefficient r. You can also |
//| change prior values with MCPDSetPrior() function. |
//| INPUT PARAMETERS: |
//| S - solver |
//| V - regularization coefficient, finite non-negative |
//| value. It is not recommended to specify zero |
//| value unless you are pretty sure that you want it.|
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDSetTikhonovRegularizer(CMCPDState &s,const double v)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(v),__FUNCTION__+": V is infinite or NAN"))
return;
//--- check
if(!CAp::Assert(v>=0.0,__FUNCTION__+": V is less than zero"))
return;
//--- change value
s.m_regterm=v;
}
//+------------------------------------------------------------------+
//| This function allows to set prior values used for regularization |
//| of your problem. |
//| By default, regularizing term is equal to r*||P-prior_P||^2, |
//| where r is a small non-zero value, P is transition matrix, |
//| prior_P is identity matrix, ||X||^2 is a sum of squared elements |
//| of X. |
//| This function allows you to change prior values prior_P. You can |
//| also change r with MCPDSetTikhonovRegularizer() function. |
//| INPUT PARAMETERS: |
//| S - solver |
//| PP - array[N,N], matrix of prior values: |
//| 1. elements must be real numbers from [0,1] |
//| 2. columns must sum to 1.0. |
//| First property is checked (exception is thrown |
//| otherwise), while second one is not |
//| checked/enforced. |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDSetPrior(CMCPDState &s,CMatrixDouble &cpp)
{
//--- create variables
int i=0;
int j=0;
int n=0;
//--- create copy of matrices
CMatrixDouble pp;
pp=cpp;
//--- initialization
n=s.m_n;
//--- check
if(!CAp::Assert(CAp::Cols(pp)>=n,__FUNCTION__+": Cols(PP)<N"))
return;
//--- check
if(!CAp::Assert(CAp::Rows(pp)>=n,__FUNCTION__+": Rows(PP)<K"))
return;
//--- calculation
for(i=0;i<=n-1;i++)
{
for(j=0;j<=n-1;j++)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(pp[i][j]),__FUNCTION__+": PP containts infinite elements"))
return;
//--- check
if(!CAp::Assert(pp[i][j]>=0.0 && pp[i][j]<=1.0,__FUNCTION__+": PP[i][j] is less than 0.0 or greater than 1.0"))
return;
//--- change value
s.m_priorp[i].Set(j,pp[i][j]);
}
}
}
//+------------------------------------------------------------------+
//| This function is used to change prediction weights |
//| MCPD solver scales prediction errors as follows |
//| Error(P) = ||W*(y-P*x)||^2 |
//| where |
//| x is a system state at time t |
//| y is a system state at time t+1 |
//| P is a transition matrix |
//| W is a diagonal scaling matrix |
//| By default, weights are chosen in order to minimize relative |
//| prediction error instead of absolute one. For example, if one |
//| component of state is about 0.5 in magnitude and another one is |
//| about 0.05, then algorithm will make corresponding weights equal |
//| to 2.0 and 20.0. |
//| INPUT PARAMETERS: |
//| S - solver |
//| PW - array[N], weights: |
//| * must be non-negative values (exception will be |
//| thrown otherwise) |
//| * zero values will be replaced by automatically |
//| chosen values |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDSetPredictionWeights(CMCPDState &s,double &pw[])
{
//--- create variables
int i=0;
int n=0;
//--- initialization
n=s.m_n;
//--- check
if(!CAp::Assert(CAp::Len(pw)>=n,__FUNCTION__+": Length(PW)<N"))
return;
//--- calculation
for(i=0;i<=n-1;i++)
{
//--- check
if(!CAp::Assert(CMath::IsFinite(pw[i]),__FUNCTION__+": PW containts infinite or NAN elements"))
return;
//--- check
if(!CAp::Assert(pw[i]>=0.0,__FUNCTION__+": PW containts negative elements"))
return;
//--- change value
s.m_pw[i]=pw[i];
}
}
//+------------------------------------------------------------------+
//| This function is used to start solution of the MCPD problem. |
//| After return from this function, you can use MCPDResults() to get|
//| solution and completion code. |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDSolve(CMCPDState &s)
{
//--- create variables
int n=0;
int npairs=0;
int ccnt=0;
int i=0;
int j=0;
int k=0;
int k2=0;
double v=0;
double vv=0;
int i_=0;
int i1_=0;
//--- initialization
n=s.m_n;
npairs=s.m_npairs;
//--- init fields of S
s.m_repterminationtype=0;
s.m_repinneriterationscount=0;
s.m_repouteriterationscount=0;
s.m_repnfev=0;
for(k=0;k<=n-1;k++)
{
for(k2=0;k2<=n-1;k2++)
s.m_p[k].Set(k2,CInfOrNaN::NaN());
}
//--- Generate "effective" weights for prediction and calculate preconditioner
for(i=0;i<=n-1;i++)
{
//--- check
if(s.m_pw[i]==0.0)
{
//--- change values
v=0;
k=0;
for(j=0;j<=npairs-1;j++)
{
//--- check
if(s.m_data[j][n+i]!=0.0)
{
v=v+s.m_data[j][n+i];
k=k+1;
}
}
//--- check
if(k!=0)
s.m_effectivew[i]=k/v;
else
s.m_effectivew[i]=1.0;
}
else
s.m_effectivew[i]=s.m_pw[i];
}
//--- calculation
for(i=0;i<=n-1;i++)
{
for(j=0;j<=n-1;j++)
s.m_h[i*n+j]=2*s.m_regterm;
}
//--- calculation
for(k=0;k<=npairs-1;k++)
{
for(i=0;i<=n-1;i++)
{
for(j=0;j<=n-1;j++)
s.m_h[i*n+j]=s.m_h[i*n+j]+2*CMath::Sqr(s.m_effectivew[i])*CMath::Sqr(s.m_data[k][j]);
}
}
//--- calculation
for(i=0;i<=n-1;i++)
{
for(j=0;j<=n-1;j++)
{
//--- check
if(s.m_h[i*n+j]==0.0)
s.m_h[i*n+j]=1;
}
}
//--- Generate "effective" BndL/BndU
for(i=0;i<=n-1;i++)
{
for(j=0;j<=n-1;j++)
{
//--- Set default boundary constraints.
//--- Lower bound is always zero,upper bound is calculated
//--- with respect to entry/exit states.
s.m_effectivebndl[i*n+j]=0.0;
//--- check
if(s.m_states[i]>0 || s.m_states[j]<0)
s.m_effectivebndu[i*n+j]=0.0;
else
s.m_effectivebndu[i*n+j]=1.0;
//--- Calculate intersection of the default and user-specified bound constraints.
//--- This code checks consistency of such combination.
if(CMath::IsFinite(s.m_bndl[i][j]) && s.m_bndl[i][j]>s.m_effectivebndl[i*n+j])
s.m_effectivebndl[i*n+j]=s.m_bndl[i][j];
//--- check
if(CMath::IsFinite(s.m_bndu[i][j]) && s.m_bndu[i][j]<s.m_effectivebndu[i*n+j])
s.m_effectivebndu[i*n+j]=s.m_bndu[i][j];
//--- check
if(s.m_effectivebndl[i*n+j]>s.m_effectivebndu[i*n+j])
{
s.m_repterminationtype=-3;
return;
}
//--- Calculate intersection of the effective bound constraints
//--- and user-specified equality constraints.
//--- This code checks consistency of such combination.
if(CMath::IsFinite(s.m_ec[i][j]))
{
//--- check
if(s.m_ec[i][j]<s.m_effectivebndl[i*n+j] || s.m_ec[i][j]>s.m_effectivebndu[i*n+j])
{
s.m_repterminationtype=-3;
return;
}
//--- change values
s.m_effectivebndl[i*n+j]=s.m_ec[i][j];
s.m_effectivebndu[i*n+j]=s.m_ec[i][j];
}
}
}
//--- Generate linear constraints:
//--- * "default" sums-to-one constraints (not generated for "exit" states)
CApServ::RMatrixSetLengthAtLeast(s.m_effectivec,s.m_ccnt+n,n*n+1);
//--- function call
CApServ::IVectorSetLengthAtLeast(s.m_effectivect,s.m_ccnt+n);
ccnt=s.m_ccnt;
for(i=0;i<=s.m_ccnt-1;i++)
{
for(j=0;j<=n*n;j++)
s.m_effectivec[i].Set(j,s.m_c[i][j]);
s.m_effectivect[i]=s.m_ct[i];
}
//--- calculation
for(i=0;i<=n-1;i++)
{
//--- check
if(s.m_states[i]>=0)
{
for(k=0;k<=n*n-1;k++)
s.m_effectivec[ccnt].Set(k,0);
for(k=0;k<=n-1;k++)
s.m_effectivec[ccnt].Set(k*n+i,1);
//--- change values
s.m_effectivec[ccnt].Set(n*n,1.0);
s.m_effectivect[ccnt]=0;
ccnt=ccnt+1;
}
}
//--- create optimizer
for(i=0;i<=n-1;i++)
{
for(j=0;j<=n-1;j++)
s.m_tmpp[i*n+j]=1.0/(double)n;
}
//--- function calls
CMinBLEIC::MinBLEICRestartFrom(s.m_bs,s.m_tmpp);
CMinBLEIC::MinBLEICSetBC(s.m_bs,s.m_effectivebndl,s.m_effectivebndu);
CMinBLEIC::MinBLEICSetLC(s.m_bs,s.m_effectivec,s.m_effectivect,ccnt);
CMinBLEIC::MinBLEICSetInnerCond(s.m_bs,0,0,m_xtol);
CMinBLEIC::MinBLEICSetOuterCond(s.m_bs,m_xtol,1.0E-5);
CMinBLEIC::MinBLEICSetPrecDiag(s.m_bs,s.m_h);
//--- solve problem
while(CMinBLEIC::MinBLEICIteration(s.m_bs))
{
//--- check
if(!CAp::Assert(s.m_bs.m_needfg,__FUNCTION__+": internal error"))
return;
//--- check
if(s.m_bs.m_needfg)
{
//--- Calculate regularization term
s.m_bs.m_f=0.0;
vv=s.m_regterm;
for(i=0;i<=n-1;i++)
{
for(j=0;j<=n-1;j++)
{
s.m_bs.m_f=s.m_bs.m_f+vv*CMath::Sqr(s.m_bs.m_x[i*n+j]-s.m_priorp[i][j]);
s.m_bs.m_g[i*n+j]=2*vv*(s.m_bs.m_x[i*n+j]-s.m_priorp[i][j]);
}
}
//--- calculate prediction error/gradient for K-th pair
for(k=0;k<=npairs-1;k++)
{
for(i=0;i<=n-1;i++)
{
i1_=(0)-(i*n);
v=0.0;
for(i_=i*n;i_<=i*n+n-1;i_++)
v+=s.m_bs.m_x[i_]*s.m_data[k][i_+i1_];
vv=s.m_effectivew[i];
s.m_bs.m_f=s.m_bs.m_f+CMath::Sqr(vv*(v-s.m_data[k][n+i]));
for(j=0;j<=n-1;j++)
{
s.m_bs.m_g[i*n+j]=s.m_bs.m_g[i*n+j]+2*vv*vv*(v-s.m_data[k][n+i])*s.m_data[k][j];
}
}
}
//--- continue
continue;
}
}
//--- function call
CMinBLEIC::MinBLEICResultsBuf(s.m_bs,s.m_tmpp,s.m_br);
for(i=0;i<=n-1;i++)
{
for(j=0;j<=n-1;j++)
s.m_p[i].Set(j,s.m_tmpp[i*n+j]);
}
//--- change values
s.m_repterminationtype=s.m_br.m_terminationtype;
s.m_repinneriterationscount=s.m_br.m_inneriterationscount;
s.m_repouteriterationscount=s.m_br.m_outeriterationscount;
s.m_repnfev=s.m_br.m_nfev;
}
//+------------------------------------------------------------------+
//| MCPD results |
//| INPUT PARAMETERS: |
//| State - algorithm state |
//| OUTPUT PARAMETERS: |
//| P - array[N,N], transition matrix |
//| Rep - optimization report. You should check Rep. |
//| TerminationType in order to distinguish successful|
//| termination from unsuccessful one. Speaking short,|
//| positive values denote success, negative ones are |
//| failures. More information about fields of this |
//| structure can befound in the comments on |
//| MCPDReport datatype. |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDResults(CMCPDState &s,CMatrixDouble &p,
CMCPDReport &rep)
{
//--- create variables
int i=0;
int j=0;
//--- allocation
p.Resize(s.m_n,s.m_n);
//--- copy
for(i=0;i<=s.m_n-1;i++)
{
for(j=0;j<=s.m_n-1;j++)
p[i].Set(j,s.m_p[i][j]);
}
//--- change values
rep.m_terminationtype=s.m_repterminationtype;
rep.m_inneriterationscount=s.m_repinneriterationscount;
rep.m_outeriterationscount=s.m_repouteriterationscount;
rep.m_nfev=s.m_repnfev;
}
//+------------------------------------------------------------------+
//| Internal initialization function |
//+------------------------------------------------------------------+
static void CMarkovCPD::MCPDInit(const int n,const int entrystate,
const int exitstate,CMCPDState &s)
{
//--- create variables
int i=0;
int j=0;
//--- check
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
return;
//--- initialization
s.m_n=n;
//--- allocation
ArrayResizeAL(s.m_states,n);
for(i=0;i<=n-1;i++)
s.m_states[i]=0;
//--- check
if(entrystate>=0)
s.m_states[entrystate]=1;
//--- check
if(exitstate>=0)
s.m_states[exitstate]=-1;
//--- initialization
s.m_npairs=0;
s.m_regterm=1.0E-8;
s.m_ccnt=0;
//--- allocation
s.m_p.Resize(n,n);
s.m_ec.Resize(n,n);
s.m_bndl.Resize(n,n);
s.m_bndu.Resize(n,n);
ArrayResizeAL(s.m_pw,n);
s.m_priorp.Resize(n,n);
ArrayResizeAL(s.m_tmpp,n*n);
ArrayResizeAL(s.m_effectivew,n);
ArrayResizeAL(s.m_effectivebndl,n*n);
ArrayResizeAL(s.m_effectivebndu,n*n);
ArrayResizeAL(s.m_h,n*n);
//--- change values
for(i=0;i<=n-1;i++)
{
for(j=0;j<=n-1;j++)
{
s.m_p[i].Set(j,0.0);
s.m_priorp[i].Set(j,0.0);
s.m_bndl[i].Set(j,CInfOrNaN::NegativeInfinity());
s.m_bndu[i].Set(j,CInfOrNaN::PositiveInfinity());
s.m_ec[i].Set(j,CInfOrNaN::NaN());
}
s.m_pw[i]=0.0;
s.m_priorp[i].Set(i,1.0);
}
//--- allocation
s.m_data.Resize(1,2*n);
for(i=0;i<=2*n-1;i++)
s.m_data[0].Set(i,0.0);
for(i=0;i<=n*n-1;i++)
s.m_tmpp[i]=0.0;
//--- function call
CMinBLEIC::MinBLEICCreate(n*n,s.m_tmpp,s.m_bs);
}
//+------------------------------------------------------------------+
//| Training report: |
//| * NGrad - number of gradient calculations |
//| * NHess - number of Hessian calculations |
//| * NCholesky - number of Cholesky decompositions |
//+------------------------------------------------------------------+
class CMLPReport
{
public:
//--- variables
int m_ngrad;
int m_nhess;
int m_ncholesky;
//--- constructor, destructor
CMLPReport(void);
~CMLPReport(void);
//--- copy
void Copy(CMLPReport &obj);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMLPReport::CMLPReport(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMLPReport::~CMLPReport(void)
{
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CMLPReport::Copy(CMLPReport &obj)
{
//--- copy variables
m_ngrad=obj.m_ngrad;
m_nhess=obj.m_nhess;
m_ncholesky=obj.m_ncholesky;
}
//+------------------------------------------------------------------+
//| Training report: |
//| * NGrad - number of gradient calculations |
//| * NHess - number of Hessian calculations |
//| * NCholesky - number of Cholesky decompositions |
//+------------------------------------------------------------------+
class CMLPReportShell
{
private:
CMLPReport m_innerobj;
public:
//--- constructors, destructor
CMLPReportShell(void);
CMLPReportShell(CMLPReport &obj);
~CMLPReportShell(void);
//--- methods
int GetNGrad(void);
void SetNGrad(const int i);
int GetNHess(void);
void SetNHess(const int i);
int GetNCholesky(void);
void SetNCholesky(const int i);
CMLPReport *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMLPReportShell::CMLPReportShell(void)
{
}
//+------------------------------------------------------------------+
//| Copy constructor |
//+------------------------------------------------------------------+
CMLPReportShell::CMLPReportShell(CMLPReport &obj)
{
//--- copy
m_innerobj.Copy(obj);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMLPReportShell::~CMLPReportShell(void)
{
}
//+------------------------------------------------------------------+
//| Returns the value of the variable ngrad |
//+------------------------------------------------------------------+
int CMLPReportShell::GetNGrad(void)
{
//--- return result
return(m_innerobj.m_ngrad);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable ngrad |
//+------------------------------------------------------------------+
void CMLPReportShell::SetNGrad(const int i)
{
//--- change value
m_innerobj.m_ngrad=i;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable nhess |
//+------------------------------------------------------------------+
int CMLPReportShell::GetNHess(void)
{
//--- return result
return(m_innerobj.m_nhess);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable nhess |
//+------------------------------------------------------------------+
void CMLPReportShell::SetNHess(const int i)
{
//--- change value
m_innerobj.m_nhess=i;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable ncholesky |
//+------------------------------------------------------------------+
int CMLPReportShell::GetNCholesky(void)
{
//--- return result
return(m_innerobj.m_ncholesky);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable ncholesky |
//+------------------------------------------------------------------+
void CMLPReportShell::SetNCholesky(const int i)
{
//--- change value
m_innerobj.m_ncholesky=i;
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CMLPReport *CMLPReportShell::GetInnerObj(void)
{
//--- return result
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Cross-validation estimates of generalization error |
//+------------------------------------------------------------------+
class CMLPCVReport
{
public:
//--- variables
double m_relclserror;
double m_avgce;
double m_rmserror;
double m_avgerror;
double m_avgrelerror;
//--- constructor, destructor
CMLPCVReport(void);
~CMLPCVReport(void);
//--- copy
void Copy(CMLPCVReport &obj);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMLPCVReport::CMLPCVReport(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMLPCVReport::~CMLPCVReport(void)
{
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CMLPCVReport::Copy(CMLPCVReport &obj)
{
//--- copy variables
m_relclserror=obj.m_relclserror;
m_avgce=obj.m_avgce;
m_rmserror=obj.m_rmserror;
m_avgerror=obj.m_avgerror;
m_avgrelerror=obj.m_avgrelerror;
}
//+------------------------------------------------------------------+
//| Cross-validation estimates of generalization error |
//+------------------------------------------------------------------+
class CMLPCVReportShell
{
private:
CMLPCVReport m_innerobj;
public:
//--- constructors, destructor
CMLPCVReportShell(void);
CMLPCVReportShell(CMLPCVReport &obj);
~CMLPCVReportShell(void);
//--- methods
double GetRelClsError(void);
void SetRelClsError(const double d);
double GetAvgCE(void);
void SetAvgCE(const double d);
double GetRMSError(void);
void SetRMSError(const double d);
double GetAvgError(void);
void SetAvgError(const double d);
double GetAvgRelError(void);
void SetAvgRelError(const double d);
CMLPCVReport *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMLPCVReportShell::CMLPCVReportShell(void)
{
}
//+------------------------------------------------------------------+
//| Copy constructor |
//+------------------------------------------------------------------+
CMLPCVReportShell::CMLPCVReportShell(CMLPCVReport &obj)
{
//--- copy
m_innerobj.Copy(obj);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMLPCVReportShell::~CMLPCVReportShell(void)
{
}
//+------------------------------------------------------------------+
//| Returns the value of the variable relclserror |
//+------------------------------------------------------------------+
double CMLPCVReportShell::GetRelClsError(void)
{
//--- return result
return(m_innerobj.m_relclserror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable relclserror |
//+------------------------------------------------------------------+
void CMLPCVReportShell::SetRelClsError(const double d)
{
//--- change value
m_innerobj.m_relclserror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgce |
//+------------------------------------------------------------------+
double CMLPCVReportShell::GetAvgCE(void)
{
//--- return result
return(m_innerobj.m_avgce);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgce |
//+------------------------------------------------------------------+
void CMLPCVReportShell::SetAvgCE(const double d)
{
//--- change value
m_innerobj.m_avgce=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable rmserror |
//+------------------------------------------------------------------+
double CMLPCVReportShell::GetRMSError(void)
{
//--- return result
return(m_innerobj.m_rmserror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable rmserror |
//+------------------------------------------------------------------+
void CMLPCVReportShell::SetRMSError(const double d)
{
//--- change value
m_innerobj.m_rmserror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgerror |
//+------------------------------------------------------------------+
double CMLPCVReportShell::GetAvgError(void)
{
//--- return result
return(m_innerobj.m_avgerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgerror |
//+------------------------------------------------------------------+
void CMLPCVReportShell::SetAvgError(const double d)
{
//--- change value
m_innerobj.m_avgerror=d;
}
//+------------------------------------------------------------------+
//| Returns the value of the variable avgrelerror |
//+------------------------------------------------------------------+
double CMLPCVReportShell::GetAvgRelError(void)
{
//--- return result
return(m_innerobj.m_avgrelerror);
}
//+------------------------------------------------------------------+
//| Changing the value of the variable avgrelerror |
//+------------------------------------------------------------------+
void CMLPCVReportShell::SetAvgRelError(const double d)
{
//--- change value
m_innerobj.m_avgrelerror=d;
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CMLPCVReport *CMLPCVReportShell::GetInnerObj(void)
{
//--- return result
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Training neural networks |
//+------------------------------------------------------------------+
class CMLPTrain
{
private:
//--- private methods
static void MLPKFoldCVGeneral(CMultilayerPerceptron &n,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,const int foldscount,const bool lmalgorithm,const double wstep,const int maxits,int &info,CMLPReport &rep,CMLPCVReport &cvrep);
static void MLPKFoldSplit(CMatrixDouble &xy,const int npoints,const int nclasses,const int foldscount,const bool stratifiedsplits,int &folds[]);
public:
//--- constant
static const double m_mindecay;
//--- constructor, destructor
CMLPTrain(void);
~CMLPTrain(void);
//--- public methods
static void MLPTrainLM(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints,double decay,const int restarts,int &info,CMLPReport &rep);
static void MLPTrainLBFGS(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints,double decay,const int restarts,const double wstep,int maxits,int &info,CMLPReport &rep);
static void MLPTrainES(CMultilayerPerceptron &network,CMatrixDouble &trnxy,const int trnsize,CMatrixDouble &valxy,const int valsize,const double decay,const int restarts,int &info,CMLPReport &rep);
static void MLPKFoldCVLBFGS(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,const double wstep,const int maxits,const int foldscount,int &info,CMLPReport &rep,CMLPCVReport &cvrep);
static void MLPKFoldCVLM(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,int foldscount,int &info,CMLPReport &rep,CMLPCVReport &cvrep);
};
//+------------------------------------------------------------------+
//| Initialize constant |
//+------------------------------------------------------------------+
const double CMLPTrain::m_mindecay=0.001;
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMLPTrain::CMLPTrain(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMLPTrain::~CMLPTrain(void)
{
}
//+------------------------------------------------------------------+
//| Neural network training using modified Levenberg-Marquardt with |
//| exact Hessian calculation and regularization. Subroutine trains |
//| neural network with restarts from random positions. Algorithm is |
//| well suited for small |
//| and medium scale problems (hundreds of weights). |
//| INPUT PARAMETERS: |
//| Network - neural network with initialized geometry |
//| XY - training set |
//| NPoints - training set size |
//| Decay - weight decay constant, >=0.001 |
//| Decay term 'Decay*||Weights||^2' is added to |
//| error function. |
//| If you don't know what Decay to choose, use |
//| 0.001. |
//| Restarts - number of restarts from random position, >0. |
//| If you don't know what Restarts to choose, |
//| use 2. |
//| OUTPUT PARAMETERS: |
//| Network - trained neural network. |
//| Info - return code: |
//| * -9, if internal matrix inverse subroutine |
//| failed |
//| * -2, if there is a point with class number |
//| outside of [0..NOut-1]. |
//| * -1, if wrong parameters specified |
//| (NPoints<0, Restarts<1). |
//| * 2, if task has been solved. |
//| Rep - training report |
//+------------------------------------------------------------------+
static void CMLPTrain::MLPTrainLM(CMultilayerPerceptron &network,CMatrixDouble &xy,
const int npoints,double decay,const int restarts,
int &info,CMLPReport &rep)
{
//--- create variables
int nin=0;
int nout=0;
int wcount=0;
double lmm_ftol=0;
double lmsteptol=0;
int i=0;
int k=0;
double v=0;
double e=0;
double enew=0;
double xnorm2=0;
double stepnorm=0;
bool spd;
double nu=0;
double lambdav=0;
double lambdaup=0;
double lambdadown=0;
int pass=0;
double ebest=0;
int invinfo=0;
int solverinfo=0;
int i_=0;
//--- creating arrays
double g[];
double d[];
double x[];
double y[];
double wbase[];
double wdir[];
double wt[];
double wx[];
double wbest[];
//--- create matrix
CMatrixDouble h;
CMatrixDouble hmod;
CMatrixDouble z;
//--- objects of classes
CMinLBFGSReport internalrep;
CMinLBFGSState state;
CMatInvReport invrep;
CDenseSolverReport solverrep;
//--- initialization
info=0;
//--- function call
CMLPBase::MLPProperties(network,nin,nout,wcount);
//--- initialization
lambdaup=10;
lambdadown=0.3;
lmm_ftol=0.001;
lmsteptol=0.001;
//--- Test for inputs
if(npoints<=0 || restarts<1)
{
info=-1;
return;
}
//--- check
if(CMLPBase::MLPIsSoftMax(network))
{
for(i=0;i<=npoints-1;i++)
{
//--- check
if((int)MathRound(xy[i][nin])<0 || (int)MathRound(xy[i][nin])>=nout)
{
info=-2;
return;
}
}
}
//--- change values
decay=MathMax(decay,m_mindecay);
info=2;
//--- Initialize data
rep.m_ngrad=0;
rep.m_nhess=0;
rep.m_ncholesky=0;
//--- General case.
//--- Prepare task and network. Allocate space.
CMLPBase::MLPInitPreprocessor(network,xy,npoints);
//--- allocation
ArrayResizeAL(g,wcount);
h.Resize(wcount,wcount);
hmod.Resize(wcount,wcount);
ArrayResizeAL(wbase,wcount);
ArrayResizeAL(wdir,wcount);
ArrayResizeAL(wbest,wcount);
ArrayResizeAL(wt,wcount);
ArrayResizeAL(wx,wcount);
//--- initialization
ebest=CMath::m_maxrealnumber;
//--- Multiple passes
for(pass=1;pass<=restarts;pass++)
{
//--- Initialize weights
CMLPBase::MLPRandomize(network);
//--- First stage of the hybrid algorithm: LBFGS
for(i_=0;i_<=wcount-1;i_++)
wbase[i_]=network.m_weights[i_];
//--- function calls
CMinLBFGS::MinLBFGSCreate(wcount,(int)(MathMin(wcount,5)),wbase,state);
CMinLBFGS::MinLBFGSSetCond(state,0,0,0,(int)(MathMax(25,wcount)));
while(CMinLBFGS::MinLBFGSIteration(state))
{
//--- gradient
for(i_=0;i_<=wcount-1;i_++)
network.m_weights[i_]=state.m_x[i_];
//--- function call
CMLPBase::MLPGradBatch(network,xy,npoints,state.m_f,state.m_g);
//--- weight decay
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=network.m_weights[i_]*network.m_weights[i_];
state.m_f=state.m_f+0.5*decay*v;
for(i_=0;i_<=wcount-1;i_++)
state.m_g[i_]=state.m_g[i_]+decay*network.m_weights[i_];
//--- next iteration
rep.m_ngrad=rep.m_ngrad+1;
}
//--- function call
CMinLBFGS::MinLBFGSResults(state,wbase,internalrep);
for(i_=0;i_<=wcount-1;i_++)
network.m_weights[i_]=wbase[i_];
//--- Second stage of the hybrid algorithm: LM
//--- Initialize H with identity matrix,
//--- G with gradient,
//--- E with regularized error.
CMLPBase::MLPHessianBatch(network,xy,npoints,e,g,h);
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=network.m_weights[i_]*network.m_weights[i_];
//--- change values
e=e+0.5*decay*v;
for(i_=0;i_<=wcount-1;i_++)
g[i_]=g[i_]+decay*network.m_weights[i_];
for(k=0;k<=wcount-1;k++)
h[k].Set(k,h[k][k]+decay);
//--- change values
rep.m_nhess=rep.m_nhess+1;
lambdav=0.001;
nu=2;
//--- cycle
while(true)
{
//--- 1. HMod=H+lambda*I
//--- 2. Try to solve (H+Lambda*I)*dx=-g.
//--- Increase lambda if left part is not positive definite.
for(i=0;i<=wcount-1;i++)
{
for(i_=0;i_<=wcount-1;i_++)
hmod[i].Set(i_,h[i][i_]);
hmod[i].Set(i,hmod[i][i]+lambdav);
}
//--- function call
spd=CTrFac::SPDMatrixCholesky(hmod,wcount,true);
rep.m_ncholesky=rep.m_ncholesky+1;
//--- check
if(!spd)
{
lambdav=lambdav*lambdaup*nu;
nu=nu*2;
continue;
}
//--- function call
CDenseSolver::SPDMatrixCholeskySolve(hmod,wcount,true,g,solverinfo,solverrep,wdir);
//--- check
if(solverinfo<0)
{
lambdav=lambdav*lambdaup*nu;
nu=nu*2;
continue;
}
for(i_=0;i_<=wcount-1;i_++)
wdir[i_]=-1*wdir[i_];
//--- Lambda found.
//--- 1. Save old w in WBase
//--- 1. Test some stopping criterions
//--- 2. If error(w+wdir)>error(w),increase lambda
for(i_=0;i_<=wcount-1;i_++)
network.m_weights[i_]=network.m_weights[i_]+wdir[i_];
xnorm2=0.0;
for(i_=0;i_<=wcount-1;i_++)
xnorm2+=network.m_weights[i_]*network.m_weights[i_];
//--- change value
stepnorm=0.0;
for(i_=0;i_<=wcount-1;i_++)
stepnorm+=wdir[i_]*wdir[i_];
stepnorm=MathSqrt(stepnorm);
//--- function call
enew=CMLPBase::MLPError(network,xy,npoints)+0.5*decay*xnorm2;
//--- check
if(stepnorm<lmsteptol*(1+MathSqrt(xnorm2)))
break;
//--- check
if(enew>e)
{
lambdav=lambdav*lambdaup*nu;
nu=nu*2;
continue;
}
//--- Optimize using inv(cholesky(H)) as preconditioner
CMatInv::RMatrixTrInverse(hmod,wcount,true,false,invinfo,invrep);
//--- check
if(invinfo<=0)
{
//--- if matrix can't be inverted then exit with errors
//--- TODO: make WCount steps in direction suggested by HMod
info=-9;
return;
}
//--- calculation
for(i_=0;i_<=wcount-1;i_++)
wbase[i_]=network.m_weights[i_];
for(i=0;i<=wcount-1;i++)
wt[i]=0;
//--- function calls
CMinLBFGS::MinLBFGSCreateX(wcount,wcount,wt,1,0.0,state);
CMinLBFGS::MinLBFGSSetCond(state,0,0,0,5);
while(CMinLBFGS::MinLBFGSIteration(state))
{
//--- gradient
for(i=0;i<=wcount-1;i++)
{
v=0.0;
for(i_=i;i_<=wcount-1;i_++)
v+=state.m_x[i_]*hmod[i][i_];
network.m_weights[i]=wbase[i]+v;
}
//--- function call
CMLPBase::MLPGradBatch(network,xy,npoints,state.m_f,g);
for(i=0;i<=wcount-1;i++)
state.m_g[i]=0;
for(i=0;i<=wcount-1;i++)
{
v=g[i];
for(i_=i;i_<=wcount-1;i_++)
state.m_g[i_]=state.m_g[i_]+v*hmod[i][i_];
}
//--- weight decay
//--- grad(x'*x)=A'*(x0+A*t)
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=network.m_weights[i_]*network.m_weights[i_];
state.m_f=state.m_f+0.5*decay*v;
for(i=0;i<=wcount-1;i++)
{
v=decay*network.m_weights[i];
for(i_=i;i_<=wcount-1;i_++)
state.m_g[i_]=state.m_g[i_]+v*hmod[i][i_];
}
//--- next iteration
rep.m_ngrad=rep.m_ngrad+1;
}
//--- function call
CMinLBFGS::MinLBFGSResults(state,wt,internalrep);
//--- Accept new position.
//--- Calculate Hessian
for(i=0;i<=wcount-1;i++)
{
v=0.0;
for(i_=i;i_<=wcount-1;i_++)
v+=wt[i_]*hmod[i][i_];
network.m_weights[i]=wbase[i]+v;
}
//--- function call
CMLPBase::MLPHessianBatch(network,xy,npoints,e,g,h);
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=network.m_weights[i_]*network.m_weights[i_];
//--- change value
e=e+0.5*decay*v;
for(i_=0;i_<=wcount-1;i_++)
g[i_]=g[i_]+decay*network.m_weights[i_];
for(k=0;k<=wcount-1;k++)
h[k].Set(k,h[k][k]+decay);
rep.m_nhess=rep.m_nhess+1;
//--- Update lambda
lambdav=lambdav*lambdadown;
nu=2;
}
//--- update WBest
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=network.m_weights[i_]*network.m_weights[i_];
//--- change value
e=0.5*decay*v+CMLPBase::MLPError(network,xy,npoints);
//--- check
if(e<ebest)
{
ebest=e;
for(i_=0;i_<=wcount-1;i_++)
wbest[i_]=network.m_weights[i_];
}
}
//--- copy WBest to output
for(i_=0;i_<=wcount-1;i_++)
network.m_weights[i_]=wbest[i_];
}
//+------------------------------------------------------------------+
//| Neural network training using L-BFGS algorithm with |
//| regularization. Subroutine trains neural network with restarts |
//| from random positions. Algorithm is well suited for problems of |
//| any dimensionality (memory requirements and step complexity are |
//| linear by weights number). |
//| INPUT PARAMETERS: |
//| Network - neural network with initialized geometry |
//| XY - training set |
//| NPoints - training set size |
//| Decay - weight decay constant, >=0.001 |
//| Decay term 'Decay*||Weights||^2' is added to |
//| error function. |
//| If you don't know what Decay to choose, use |
//| 0.001. |
//| Restarts - number of restarts from random position, >0. |
//| If you don't know what Restarts to choose, |
//| use 2. |
//| WStep - stopping criterion. Algorithm stops if step |
//| size is less than WStep. Recommended |
//| value - 0.01. Zero step size means stopping |
//| after MaxIts iterations. |
//| MaxIts - stopping criterion. Algorithm stops after |
//| MaxIts iterations (NOT gradient calculations).|
//| Zero MaxIts means stopping when step is |
//| sufficiently small. |
//| OUTPUT PARAMETERS: |
//| Network - trained neural network. |
//| Info - return code: |
//| * -8, if both WStep=0 and MaxIts=0 |
//| * -2, if there is a point with class number |
//| outside of [0..NOut-1]. |
//| * -1, if wrong parameters specified |
//| (NPoints<0, Restarts<1). |
//| * 2, if task has been solved. |
//| Rep - training report |
//+------------------------------------------------------------------+
static void CMLPTrain::MLPTrainLBFGS(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int npoints,
double decay,const int restarts,
const double wstep,int maxits,
int &info,CMLPReport &rep)
{
//--- create variables
int i=0;
int pass=0;
int nin=0;
int nout=0;
int wcount=0;
double e=0;
double v=0;
double ebest=0;
//--- creating arrays
double w[];
double wbest[];
//--- create objects of classes
CMinLBFGSReport internalrep;
CMinLBFGSState state;
int i_=0;
//--- initialization
info=0;
//--- Test inputs,parse flags,read network geometry
if(wstep==0.0 && maxits==0)
{
info=-8;
return;
}
//--- check
if(((npoints<=0 || restarts<1) || wstep<0.0) || maxits<0)
{
info=-1;
return;
}
//--- function call
CMLPBase::MLPProperties(network,nin,nout,wcount);
//--- check
if(CMLPBase::MLPIsSoftMax(network))
{
for(i=0;i<=npoints-1;i++)
{
//--- check
if((int)MathRound(xy[i][nin])<0 || (int)MathRound(xy[i][nin])>=nout)
{
info=-2;
return;
}
}
}
//--- change values
decay=MathMax(decay,m_mindecay);
info=2;
//--- Prepare
CMLPBase::MLPInitPreprocessor(network,xy,npoints);
//--- allocation
ArrayResizeAL(w,wcount);
ArrayResizeAL(wbest,wcount);
//--- initialization
ebest=CMath::m_maxrealnumber;
//--- Multiple starts
rep.m_ncholesky=0;
rep.m_nhess=0;
rep.m_ngrad=0;
for(pass=1;pass<=restarts;pass++)
{
//--- Process
CMLPBase::MLPRandomize(network);
for(i_=0;i_<=wcount-1;i_++)
w[i_]=network.m_weights[i_];
//--- function calls
CMinLBFGS::MinLBFGSCreate(wcount,(int)(MathMin(wcount,10)),w,state);
CMinLBFGS::MinLBFGSSetCond(state,0.0,0.0,wstep,maxits);
while(CMinLBFGS::MinLBFGSIteration(state))
{
for(i_=0;i_<=wcount-1;i_++)
network.m_weights[i_]=state.m_x[i_];
//--- function call
CMLPBase::MLPGradNBatch(network,xy,npoints,state.m_f,state.m_g);
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=network.m_weights[i_]*network.m_weights[i_];
state.m_f=state.m_f+0.5*decay*v;
for(i_=0;i_<=wcount-1;i_++)
state.m_g[i_]=state.m_g[i_]+decay*network.m_weights[i_];
rep.m_ngrad=rep.m_ngrad+1;
}
//--- function call
CMinLBFGS::MinLBFGSResults(state,w,internalrep);
for(i_=0;i_<=wcount-1;i_++)
network.m_weights[i_]=w[i_];
//--- Compare with best
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=network.m_weights[i_]*network.m_weights[i_];
//--- change value
e=CMLPBase::MLPErrorN(network,xy,npoints)+0.5*decay*v;
//--- check
if(e<ebest)
{
for(i_=0;i_<=wcount-1;i_++)
wbest[i_]=network.m_weights[i_];
ebest=e;
}
}
//--- The best network
for(i_=0;i_<=wcount-1;i_++)
network.m_weights[i_]=wbest[i_];
}
//+------------------------------------------------------------------+
//| Neural network training using early stopping (base algorithm - |
//| L-BFGS with regularization). |
//| INPUT PARAMETERS: |
//| Network - neural network with initialized geometry |
//| TrnXY - training set |
//| TrnSize - training set size |
//| ValXY - validation set |
//| ValSize - validation set size |
//| Decay - weight decay constant, >=0.001 |
//| Decay term 'Decay*||Weights||^2' is added to |
//| error function. |
//| If you don't know what Decay to choose, use |
//| 0.001. |
//| Restarts - number of restarts from random position, >0. |
//| If you don't know what Restarts to choose, |
//| use 2. |
//| OUTPUT PARAMETERS: |
//| Network - trained neural network. |
//| Info - return code: |
//| * -2, if there is a point with class number |
//| outside of [0..NOut-1]. |
//| * -1, if wrong parameters specified |
//| (NPoints<0, Restarts<1, ...). |
//| * 2, task has been solved, stopping |
//| criterion met - sufficiently small |
//| step size. Not expected (we use EARLY |
//| stopping) but possible and not an error|
//| * 6, task has been solved, stopping |
//| criterion met - increasing of |
//| validation set error. |
//| Rep - training report |
//| NOTE: |
//| Algorithm stops if validation set error increases for a long |
//| enough or step size is small enought (there are task where |
//| validation set may decrease for eternity). In any case solution |
//| returned corresponds to the minimum of validation set error. |
//+------------------------------------------------------------------+
static void CMLPTrain::MLPTrainES(CMultilayerPerceptron &network,
CMatrixDouble &trnxy,const int trnsize,
CMatrixDouble &valxy,const int valsize,
const double decay,const int restarts,
int &info,CMLPReport &rep)
{
//--- create variables
int i=0;
int pass=0;
int nin=0;
int nout=0;
int wcount=0;
double e=0;
double v=0;
double ebest=0;
int itbest=0;
double wstep=0;
int i_=0;
//--- creating arrays
double w[];
double wbest[];
double wfinal[];
double efinal=0;
//--- objects of classes
CMinLBFGSReport internalrep;
CMinLBFGSState state;
//--- initialization
info=0;
wstep=0.001;
//--- Test inputs,parse flags,read network geometry
if(((trnsize<=0 || valsize<=0) || restarts<1) || decay<0.0)
{
info=-1;
return;
}
//--- function call
CMLPBase::MLPProperties(network,nin,nout,wcount);
//--- check
if(CMLPBase::MLPIsSoftMax(network))
{
for(i=0;i<=trnsize-1;i++)
{
//--- check
if((int)MathRound(trnxy[i][nin])<0 || (int)MathRound(trnxy[i][nin])>=nout)
{
info=-2;
return;
}
}
for(i=0;i<=valsize-1;i++)
{
//--- check
if((int)MathRound(valxy[i][nin])<0 || (int)MathRound(valxy[i][nin])>=nout)
{
info=-2;
return;
}
}
}
//--- change value
info=2;
//--- Prepare
CMLPBase::MLPInitPreprocessor(network,trnxy,trnsize);
//--- allocation
ArrayResizeAL(w,wcount);
ArrayResizeAL(wbest,wcount);
ArrayResizeAL(wfinal,wcount);
//--- initialization
efinal=CMath::m_maxrealnumber;
for(i=0;i<=wcount-1;i++)
wfinal[i]=0;
//--- Multiple starts
rep.m_ncholesky=0;
rep.m_nhess=0;
rep.m_ngrad=0;
//--- calculation
for(pass=1;pass<=restarts;pass++)
{
//--- Process
CMLPBase::MLPRandomize(network);
//--- change values
ebest=CMLPBase::MLPError(network,valxy,valsize);
for(i_=0;i_<=wcount-1;i_++)
wbest[i_]=network.m_weights[i_];
//--- change values
itbest=0;
for(i_=0;i_<=wcount-1;i_++)
w[i_]=network.m_weights[i_];
//--- function calls
CMinLBFGS::MinLBFGSCreate(wcount,(int)(MathMin(wcount,10)),w,state);
CMinLBFGS::MinLBFGSSetCond(state,0.0,0.0,wstep,0);
CMinLBFGS::MinLBFGSSetXRep(state,true);
while(CMinLBFGS::MinLBFGSIteration(state))
{
//--- Calculate gradient
for(i_=0;i_<=wcount-1;i_++)
network.m_weights[i_]=state.m_x[i_];
//--- function call
CMLPBase::MLPGradNBatch(network,trnxy,trnsize,state.m_f,state.m_g);
v=0.0;
for(i_=0;i_<=wcount-1;i_++)
v+=network.m_weights[i_]*network.m_weights[i_];
state.m_f=state.m_f+0.5*decay*v;
for(i_=0;i_<=wcount-1;i_++)
state.m_g[i_]=state.m_g[i_]+decay*network.m_weights[i_];
rep.m_ngrad=rep.m_ngrad+1;
//--- Validation set
if(state.m_xupdated)
{
for(i_=0;i_<=wcount-1;i_++)
network.m_weights[i_]=w[i_];
//--- function call
e=CMLPBase::MLPError(network,valxy,valsize);
//--- check
if(e<ebest)
{
ebest=e;
for(i_=0;i_<=wcount-1;i_++)
wbest[i_]=network.m_weights[i_];
itbest=internalrep.m_iterationscount;
}
//--- check
if(internalrep.m_iterationscount>30 && (double)(internalrep.m_iterationscount)>(double)(1.5*itbest))
{
info=6;
break;
}
}
}
//--- function call
CMinLBFGS::MinLBFGSResults(state,w,internalrep);
//--- Compare with final answer
if(ebest<efinal)
{
for(i_=0;i_<=wcount-1;i_++)
wfinal[i_]=wbest[i_];
efinal=ebest;
}
}
//--- The best network
for(i_=0;i_<=wcount-1;i_++)
network.m_weights[i_]=wfinal[i_];
}
//+------------------------------------------------------------------+
//| Cross-validation estimate of generalization error. |
//| Base algorithm - L-BFGS. |
//| INPUT PARAMETERS: |
//| Network - neural network with initialized geometry. |
//| Network is not changed during |
//| cross-validation - it is used only as a |
//| representative of its architecture. |
//| XY - training set. |
//| SSize - training set size |
//| Decay - weight decay, same as in MLPTrainLBFGS |
//| Restarts - number of restarts, >0. |
//| restarts are counted for each partition |
//| separately, so total number of restarts will |
//| be Restarts*FoldsCount. |
//| WStep - stopping criterion, same as in MLPTrainLBFGS |
//| MaxIts - stopping criterion, same as in MLPTrainLBFGS |
//| FoldsCount - number of folds in k-fold cross-validation, |
//| 2<=FoldsCount<=SSize. |
//| recommended value: 10. |
//| OUTPUT PARAMETERS: |
//| Info - return code, same as in MLPTrainLBFGS |
//| Rep - report, same as in MLPTrainLM/MLPTrainLBFGS |
//| CVRep - generalization error estimates |
//+------------------------------------------------------------------+
static void CMLPTrain::MLPKFoldCVLBFGS(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int npoints,
const double decay,const int restarts,
const double wstep,const int maxits,
const int foldscount,int &info,
CMLPReport &rep,CMLPCVReport &cvrep)
{
//--- initialization
info=0;
//--- function call
MLPKFoldCVGeneral(network,xy,npoints,decay,restarts,foldscount,false,wstep,maxits,info,rep,cvrep);
}
//+------------------------------------------------------------------+
//| Cross-validation estimate of generalization error. |
//| Base algorithm - Levenberg-Marquardt. |
//| INPUT PARAMETERS: |
//| Network - neural network with initialized geometry. |
//| Network is not changed during |
//| cross-validation - it is used only as a |
//| representative of its architecture. |
//| XY - training set. |
//| SSize - training set size |
//| Decay - weight decay, same as in MLPTrainLBFGS |
//| Restarts - number of restarts, >0. |
//| restarts are counted for each partition |
//| separately, so total number of restarts will |
//| be Restarts*FoldsCount. |
//| FoldsCount - number of folds in k-fold cross-validation, |
//| 2<=FoldsCount<=SSize. |
//| recommended value: 10. |
//| OUTPUT PARAMETERS: |
//| Info - return code, same as in MLPTrainLBFGS |
//| Rep - report, same as in MLPTrainLM/MLPTrainLBFGS |
//| CVRep - generalization error estimates |
//+------------------------------------------------------------------+
static void CMLPTrain::MLPKFoldCVLM(CMultilayerPerceptron &network,
CMatrixDouble &xy,const int npoints,
const double decay,const int restarts,
int foldscount,int &info,CMLPReport &rep,
CMLPCVReport &cvrep)
{
//--- initialization
info=0;
//--- function call
MLPKFoldCVGeneral(network,xy,npoints,decay,restarts,foldscount,true,0.0,0,info,rep,cvrep);
}
//+------------------------------------------------------------------+
//| Internal cross-validation subroutine |
//+------------------------------------------------------------------+
static void CMLPTrain::MLPKFoldCVGeneral(CMultilayerPerceptron &n,
CMatrixDouble &xy,const int npoints,
const double decay,const int restarts,
const int foldscount,const bool lmalgorithm,
const double wstep,const int maxits,
int &info,CMLPReport &rep,
CMLPCVReport &cvrep)
{
//--- create variables
int i=0;
int fold=0;
int j=0;
int k=0;
int nin=0;
int nout=0;
int rowlen=0;
int wcount=0;
int nclasses=0;
int tssize=0;
int cvssize=0;
int relcnt=0;
int i_=0;
//--- creating arrays
int folds[];
double x[];
double y[];
//--- create matrix
CMatrixDouble cvset;
CMatrixDouble testset;
//--- creating arrays
CMultilayerPerceptron network;
CMLPReport internalrep;
//--- initialization
info=0;
//--- Read network geometry,test parameters
CMLPBase::MLPProperties(n,nin,nout,wcount);
//--- check
if(CMLPBase::MLPIsSoftMax(n))
{
nclasses=nout;
rowlen=nin+1;
}
else
{
nclasses=-nout;
rowlen=nin+nout;
}
//--- check
if((npoints<=0 || foldscount<2) || foldscount>npoints)
{
info=-1;
return;
}
//--- function call
CMLPBase::MLPCopy(n,network);
//--- K-fold out cross-validation.
//--- First,estimate generalization error
testset.Resize(npoints,rowlen);
cvset.Resize(npoints,rowlen);
ArrayResizeAL(x,nin);
ArrayResizeAL(y,nout);
//--- function call
MLPKFoldSplit(xy,npoints,nclasses,foldscount,false,folds);
//--- change values
cvrep.m_relclserror=0;
cvrep.m_avgce=0;
cvrep.m_rmserror=0;
cvrep.m_avgerror=0;
cvrep.m_avgrelerror=0;
rep.m_ngrad=0;
rep.m_nhess=0;
rep.m_ncholesky=0;
relcnt=0;
//--- calculation
for(fold=0;fold<=foldscount-1;fold++)
{
//--- Separate set
tssize=0;
cvssize=0;
for(i=0;i<=npoints-1;i++)
{
//--- check
if(folds[i]==fold)
{
for(i_=0;i_<=rowlen-1;i_++)
testset[tssize].Set(i_,xy[i][i_]);
tssize=tssize+1;
}
else
{
for(i_=0;i_<=rowlen-1;i_++)
cvset[cvssize].Set(i_,xy[i][i_]);
cvssize=cvssize+1;
}
}
//--- Train on CV training set
if(lmalgorithm)
MLPTrainLM(network,cvset,cvssize,decay,restarts,info,internalrep);
else
MLPTrainLBFGS(network,cvset,cvssize,decay,restarts,wstep,maxits,info,internalrep);
//--- check
if(info<0)
{
//--- change values
cvrep.m_relclserror=0;
cvrep.m_avgce=0;
cvrep.m_rmserror=0;
cvrep.m_avgerror=0;
cvrep.m_avgrelerror=0;
//--- exit the function
return;
}
//--- change values
rep.m_ngrad=rep.m_ngrad+internalrep.m_ngrad;
rep.m_nhess=rep.m_nhess+internalrep.m_nhess;
rep.m_ncholesky=rep.m_ncholesky+internalrep.m_ncholesky;
//--- Estimate error using CV test set
if(CMLPBase::MLPIsSoftMax(network))
{
//--- classification-only code
cvrep.m_relclserror=cvrep.m_relclserror+CMLPBase::MLPClsError(network,testset,tssize);
cvrep.m_avgce=cvrep.m_avgce+CMLPBase::MLPErrorN(network,testset,tssize);
}
//--- calculation
for(i=0;i<=tssize-1;i++)
{
for(i_=0;i_<=nin-1;i_++)
x[i_]=testset[i][i_];
//--- function call
CMLPBase::MLPProcess(network,x,y);
//--- check
if(CMLPBase::MLPIsSoftMax(network))
{
//--- Classification-specific code
k=(int)MathRound(testset[i][nin]);
for(j=0;j<=nout-1;j++)
{
//--- check
if(j==k)
{
//--- change values
cvrep.m_rmserror=cvrep.m_rmserror+CMath::Sqr(y[j]-1);
cvrep.m_avgerror=cvrep.m_avgerror+MathAbs(y[j]-1);
cvrep.m_avgrelerror=cvrep.m_avgrelerror+MathAbs(y[j]-1);
relcnt=relcnt+1;
}
else
{
//--- change values
cvrep.m_rmserror=cvrep.m_rmserror+CMath::Sqr(y[j]);
cvrep.m_avgerror=cvrep.m_avgerror+MathAbs(y[j]);
}
}
}
else
{
//--- Regression-specific code
for(j=0;j<=nout-1;j++)
{
cvrep.m_rmserror=cvrep.m_rmserror+CMath::Sqr(y[j]-testset[i][nin+j]);
cvrep.m_avgerror=cvrep.m_avgerror+MathAbs(y[j]-testset[i][nin+j]);
//--- check
if(testset[i][nin+j]!=0.0)
{
cvrep.m_avgrelerror=cvrep.m_avgrelerror+MathAbs((y[j]-testset[i][nin+j])/testset[i][nin+j]);
relcnt=relcnt+1;
}
}
}
}
}
//--- check
if(CMLPBase::MLPIsSoftMax(network))
{
cvrep.m_relclserror=cvrep.m_relclserror/npoints;
cvrep.m_avgce=cvrep.m_avgce/(MathLog(2)*npoints);
}
//--- change values
cvrep.m_rmserror=MathSqrt(cvrep.m_rmserror/(npoints*nout));
cvrep.m_avgerror=cvrep.m_avgerror/(npoints*nout);
cvrep.m_avgrelerror=cvrep.m_avgrelerror/relcnt;
info=1;
}
//+------------------------------------------------------------------+
//| Subroutine prepares K-fold split of the training set. |
//| NOTES: |
//| "NClasses>0" means that we have classification task. |
//| "NClasses<0" means regression task with -NClasses real |
//| outputs. |
//+------------------------------------------------------------------+
static void CMLPTrain::MLPKFoldSplit(CMatrixDouble &xy,const int npoints,
const int nclasses,const int foldscount,
const bool stratifiedsplits,int &folds[])
{
//--- create variables
int i=0;
int j=0;
int k=0;
//--- test parameters
if(!CAp::Assert(npoints>0,__FUNCTION__+": wrong NPoints!"))
return;
//--- check
if(!CAp::Assert(nclasses>1 || nclasses<0,__FUNCTION__+": wrong NClasses!"))
return;
//--- check
if(!CAp::Assert(foldscount>=2 && foldscount<=npoints,__FUNCTION__+" wrong FoldsCount!"))
return;
//--- check
if(!CAp::Assert(!stratifiedsplits,__FUNCTION__+": stratified splits are not supported!"))
return;
//--- Folds
ArrayResizeAL(folds,npoints);
for(i=0;i<=npoints-1;i++)
folds[i]=i*foldscount/npoints;
//--- calculation
for(i=0;i<=npoints-2;i++)
{
j=i+CMath::RandomInteger(npoints-i);
//--- check
if(j!=i)
{
k=folds[i];
folds[i]=folds[j];
folds[j]=k;
}
}
}
//+------------------------------------------------------------------+
//| Neural networks ensemble |
//+------------------------------------------------------------------+
class CMLPEnsemble
{
public:
//--- variables
int m_ensemblesize;
int m_nin;
int m_nout;
int m_wcount;
bool m_issoftmax;
bool m_postprocessing;
int m_serializedlen;
//--- arrays
int m_structinfo[];
double m_weights[];
double m_columnmeans[];
double m_columnsigmas[];
double m_serializedmlp[];
double m_tmpweights[];
double m_tmpmeans[];
double m_tmpsigmas[];
double m_neurons[];
double m_dfdnet[];
double m_y[];
//--- constructor, destructor
CMLPEnsemble(void);
~CMLPEnsemble(void);
//--- copy
void Copy(CMLPEnsemble &obj);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMLPEnsemble::CMLPEnsemble(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMLPEnsemble::~CMLPEnsemble(void)
{
}
//+------------------------------------------------------------------+
//| Copy |
//+------------------------------------------------------------------+
void CMLPEnsemble::Copy(CMLPEnsemble &obj)
{
//--- copy variables
m_ensemblesize=obj.m_ensemblesize;
m_nin=obj.m_nin;
m_nout=obj.m_nout;
m_wcount=obj.m_wcount;
m_issoftmax=obj.m_issoftmax;
m_postprocessing=obj.m_postprocessing;
m_serializedlen=obj.m_serializedlen;
//--- copy arrays
ArrayCopy(m_structinfo,obj.m_structinfo);
ArrayCopy(m_weights,obj.m_weights);
ArrayCopy(m_columnmeans,obj.m_columnmeans);
ArrayCopy(m_columnsigmas,obj.m_columnsigmas);
ArrayCopy(m_serializedmlp,obj.m_serializedmlp);
ArrayCopy(m_tmpweights,obj.m_tmpweights);
ArrayCopy(m_tmpmeans,obj.m_tmpmeans);
ArrayCopy(m_tmpsigmas,obj.m_tmpsigmas);
ArrayCopy(m_neurons,obj.m_neurons);
ArrayCopy(m_dfdnet,obj.m_dfdnet);
ArrayCopy(m_y,obj.m_y);
}
//+------------------------------------------------------------------+
//| Neural networks ensemble |
//+------------------------------------------------------------------+
class CMLPEnsembleShell
{
private:
CMLPEnsemble m_innerobj;
public:
//--- constructors, destructor
CMLPEnsembleShell(void);
CMLPEnsembleShell(CMLPEnsemble &obj);
~CMLPEnsembleShell(void);
//--- method
CMLPEnsemble *GetInnerObj(void);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMLPEnsembleShell::CMLPEnsembleShell(void)
{
}
//+------------------------------------------------------------------+
//| Copy constructor |
//+------------------------------------------------------------------+
CMLPEnsembleShell::CMLPEnsembleShell(CMLPEnsemble &obj)
{
//--- copy
m_innerobj.Copy(obj);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMLPEnsembleShell::~CMLPEnsembleShell(void)
{
}
//+------------------------------------------------------------------+
//| Return object of class |
//+------------------------------------------------------------------+
CMLPEnsemble *CMLPEnsembleShell::GetInnerObj(void)
{
//--- return result
return(GetPointer(m_innerobj));
}
//+------------------------------------------------------------------+
//| Neural networks ensemble |
//+------------------------------------------------------------------+
class CMLPE
{
private:
//--- private methods
static void MLPEAllErrors(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints,double &relcls,double &avgce,double &rms,double &avg,double &avgrel);
static void MLPEBaggingInternal(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,const double wstep,const int maxits,const bool lmalgorithm,int &info,CMLPReport &rep,CMLPCVReport &ooberrors);
public:
//--- class constants
static const int m_mlpntotaloffset;
static const int m_mlpevnum;
//--- constructor, destructor
CMLPE(void);
~CMLPE(void);
//--- public methods
static void MLPECreate0(const int nin,const int nout,const int ensemblesize,CMLPEnsemble &ensemble);
static void MLPECreate1(const int nin,const int nhid,const int nout,const int ensemblesize,CMLPEnsemble &ensemble);
static void MLPECreate2(const int nin,const int nhid1,const int nhid2,const int nout,const int ensemblesize,CMLPEnsemble &ensemble);
static void MLPECreateB0(const int nin,const int nout,const double b,const double d,const int ensemblesize,CMLPEnsemble &ensemble);
static void MLPECreateB1(const int nin,const int nhid,const int nout,const double b,const double d,const int ensemblesize,CMLPEnsemble &ensemble);
static void MLPECreateB2(const int nin,const int nhid1,const int nhid2,const int nout,const double b,const double d,const int ensemblesize,CMLPEnsemble &ensemble);
static void MLPECreateR0(const int nin,const int nout,const double a,const double b,const int ensemblesize,CMLPEnsemble &ensemble);
static void MLPECreateR1(const int nin,const int nhid,const int nout,const double a,const double b,const int ensemblesize,CMLPEnsemble &ensemble);
static void MLPECreateR2(const int nin,const int nhid1,const int nhid2,const int nout,const double a,const double b,const int ensemblesize,CMLPEnsemble &ensemble);
static void MLPECreateC0(const int nin,const int nout,const int ensemblesize,CMLPEnsemble &ensemble);
static void MLPECreateC1(const int nin,const int nhid,const int nout,const int ensemblesize,CMLPEnsemble &ensemble);
static void MLPECreateC2(const int nin,const int nhid1,const int nhid2,const int nout,const int ensemblesize,CMLPEnsemble &ensemble);
static void MLPECreateFromNetwork(CMultilayerPerceptron &network,const int ensemblesize,CMLPEnsemble &ensemble);
static void MLPECopy(CMLPEnsemble &ensemble1,CMLPEnsemble &ensemble2);
static void MLPESerialize(CMLPEnsemble &ensemble,double &ra[],int &rlen);
static void MLPEUnserialize(double &ra[],CMLPEnsemble &ensemble);
static void MLPERandomize(CMLPEnsemble &ensemble);
static void MLPEProperties(CMLPEnsemble &ensemble,int &nin,int &nout);
static bool MLPEIsSoftMax(CMLPEnsemble &ensemble);
static void MLPEProcess(CMLPEnsemble &ensemble,double &x[],double &y[]);
static void MLPEProcessI(CMLPEnsemble &ensemble,double &x[],double &y[]);
static double MLPERelClsError(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints);
static double MLPEAvgCE(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints);
static double MLPERMSError(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints);
static double MLPEAvgError(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints);
static double MLPEAvgRelError(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints);
static void MLPEBaggingLM(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,int &info,CMLPReport &rep,CMLPCVReport &ooberrors);
static void MLPEBaggingLBFGS(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,const double wstep,const int maxits,int &info,CMLPReport &rep,CMLPCVReport &ooberrors);
static void MLPETrainES(CMLPEnsemble &ensemble,CMatrixDouble &xy,const int npoints,const double decay,const int restarts,int &info,CMLPReport &rep);
};
//+------------------------------------------------------------------+
//| Initialize constants |
//+------------------------------------------------------------------+
const int CMLPE::m_mlpntotaloffset=3;
const int CMLPE::m_mlpevnum=9;
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CMLPE::CMLPE(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CMLPE::~CMLPE(void)
{
}
//+------------------------------------------------------------------+
//| Like MLPCreate0, but for ensembles. |
//+------------------------------------------------------------------+
static void CMLPE::MLPECreate0(const int nin,const int nout,const int ensemblesize,
CMLPEnsemble &ensemble)
{
//--- object of class
CMultilayerPerceptron net;
//--- function call
CMLPBase::MLPCreate0(nin,nout,net);
//--- function call
MLPECreateFromNetwork(net,ensemblesize,ensemble);
}
//+------------------------------------------------------------------+
//| Like MLPCreate1, but for ensembles. |
//+------------------------------------------------------------------+
static void CMLPE::MLPECreate1(const int nin,const int nhid,const int nout,
const int ensemblesize,CMLPEnsemble &ensemble)
{
//--- object of class
CMultilayerPerceptron net;
//--- function call
CMLPBase::MLPCreate1(nin,nhid,nout,net);
//--- function call
MLPECreateFromNetwork(net,ensemblesize,ensemble);
}
//+------------------------------------------------------------------+
//| Like MLPCreate2, but for ensembles. |
//+------------------------------------------------------------------+
static void CMLPE::MLPECreate2(const int nin,const int nhid1,const int nhid2,
const int nout,const int ensemblesize,
CMLPEnsemble &ensemble)
{
//--- object of class
CMultilayerPerceptron net;
//--- function call
CMLPBase::MLPCreate2(nin,nhid1,nhid2,nout,net);
//--- function call
MLPECreateFromNetwork(net,ensemblesize,ensemble);
}
//+------------------------------------------------------------------+
//| Like MLPCreateB0, but for ensembles. |
//+------------------------------------------------------------------+
static void CMLPE::MLPECreateB0(const int nin,const int nout,const double b,
const double d,const int ensemblesize,
CMLPEnsemble &ensemble)
{
//--- object of class
CMultilayerPerceptron net;
//--- function call
CMLPBase::MLPCreateB0(nin,nout,b,d,net);
//--- function call
MLPECreateFromNetwork(net,ensemblesize,ensemble);
}
//+------------------------------------------------------------------+
//| Like MLPCreateB1, but for ensembles. |
//+------------------------------------------------------------------+
static void CMLPE::MLPECreateB1(const int nin,const int nhid,const int nout,
const double b,const double d,const int ensemblesize,
CMLPEnsemble &ensemble)
{
//--- object of class
CMultilayerPerceptron net;
//--- function call
CMLPBase::MLPCreateB1(nin,nhid,nout,b,d,net);
//--- function call
MLPECreateFromNetwork(net,ensemblesize,ensemble);
}
//+------------------------------------------------------------------+
//| Like MLPCreateB2, but for ensembles. |
//+------------------------------------------------------------------+
static void CMLPE::MLPECreateB2(const int nin,const int nhid1,const int nhid2,
const int nout,const double b,const double d,
const int ensemblesize,CMLPEnsemble &ensemble)
{
//--- object of class
CMultilayerPerceptron net;
//--- function call
CMLPBase::MLPCreateB2(nin,nhid1,nhid2,nout,b,d,net);
//--- function call
MLPECreateFromNetwork(net,ensemblesize,ensemble);
}
//+------------------------------------------------------------------+
//| Like MLPCreateR0, but for ensembles. |
//+------------------------------------------------------------------+
static void CMLPE::MLPECreateR0(const int nin,const int nout,const double a,
const double b,const int ensemblesize,
CMLPEnsemble &ensemble)
{
//--- object of class
CMultilayerPerceptron net;
//--- function call
CMLPBase::MLPCreateR0(nin,nout,a,b,net);
//--- function call
MLPECreateFromNetwork(net,ensemblesize,ensemble);
}
//+------------------------------------------------------------------+
//| Like MLPCreateR1, but for ensembles. |
//+------------------------------------------------------------------+
static void CMLPE::MLPECreateR1(const int nin,const int nhid,const int nout,
const double a,const double b,
const int ensemblesize,CMLPEnsemble &ensemble)
{
//--- object of class
CMultilayerPerceptron net;
//--- function call
CMLPBase::MLPCreateR1(nin,nhid,nout,a,b,net);
//--- function call
MLPECreateFromNetwork(net,ensemblesize,ensemble);
}
//+------------------------------------------------------------------+
//| Like MLPCreateR2, but for ensembles. |
//+------------------------------------------------------------------+
static void CMLPE::MLPECreateR2(const int nin,const int nhid1,const int nhid2,
const int nout,const double a,const double b,
const int ensemblesize,CMLPEnsemble &ensemble)
{
//--- object of class
CMultilayerPerceptron net;
//--- function call
CMLPBase::MLPCreateR2(nin,nhid1,nhid2,nout,a,b,net);
//--- function call
MLPECreateFromNetwork(net,ensemblesize,ensemble);
}
//+------------------------------------------------------------------+
//| Like MLPCreateC0, but for ensembles. |
//+------------------------------------------------------------------+
static void CMLPE::MLPECreateC0(const int nin,const int nout,const int ensemblesize,
CMLPEnsemble &ensemble)
{
//--- object of class
CMultilayerPerceptron net;
//--- function call
CMLPBase::MLPCreateC0(nin,nout,net);
//--- function call
MLPECreateFromNetwork(net,ensemblesize,ensemble);
}
//+------------------------------------------------------------------+
//| Like MLPCreateC1, but for ensembles. |
//+------------------------------------------------------------------+
static void CMLPE::MLPECreateC1(const int nin,const int nhid,const int nout,
const int ensemblesize,CMLPEnsemble &ensemble)
{
//--- object of class
CMultilayerPerceptron net;
//--- function call
CMLPBase::MLPCreateC1(nin,nhid,nout,net);
//--- function call
MLPECreateFromNetwork(net,ensemblesize,ensemble);
}
//+------------------------------------------------------------------+
//| Like MLPCreateC2, but for ensembles. |
//+------------------------------------------------------------------+
static void CMLPE::MLPECreateC2(const int nin,const int nhid1,const int nhid2,
const int nout,const int ensemblesize,
CMLPEnsemble &ensemble)
{
//--- object of class
CMultilayerPerceptron net;
//--- function call
CMLPBase::MLPCreateC2(nin,nhid1,nhid2,nout,net);
//--- function call
MLPECreateFromNetwork(net,ensemblesize,ensemble);
}
//+------------------------------------------------------------------+
//| Creates ensemble from network. Only network geometry is copied. |
//+------------------------------------------------------------------+
static void CMLPE::MLPECreateFromNetwork(CMultilayerPerceptron &network,
const int ensemblesize,
CMLPEnsemble &ensemble)
{
//--- create variables
int i=0;
int ccount=0;
int i_=0;
int i1_=0;
//--- check
if(!CAp::Assert(ensemblesize>0,__FUNCTION__+": incorrect ensemble size!"))
return;
//--- network properties
CMLPBase::MLPProperties(network,ensemble.m_nin,ensemble.m_nout,ensemble.m_wcount);
//--- check
if(CMLPBase::MLPIsSoftMax(network))
ccount=ensemble.m_nin;
else
ccount=ensemble.m_nin+ensemble.m_nout;
//--- change values
ensemble.m_postprocessing=false;
ensemble.m_issoftmax=CMLPBase::MLPIsSoftMax(network);
ensemble.m_ensemblesize=ensemblesize;
//--- structure information
ArrayResizeAL(ensemble.m_structinfo,network.m_structinfo[0]);
//--- copy
for(i=0;i<=network.m_structinfo[0]-1;i++)
ensemble.m_structinfo[i]=network.m_structinfo[i];
//--- weights,means,sigmas
ArrayResizeAL(ensemble.m_weights,ensemblesize*ensemble.m_wcount);
ArrayResizeAL(ensemble.m_columnmeans,ensemblesize*ccount);
ArrayResizeAL(ensemble.m_columnsigmas,ensemblesize*ccount);
//--- calculation
for(i=0;i<=ensemblesize*ensemble.m_wcount-1;i++)
ensemble.m_weights[i]=CMath::RandomReal()-0.5;
//--- calculation
for(i=0;i<=ensemblesize-1;i++)
{
i1_=-(i*ccount);
for(i_=i*ccount;i_<=(i+1)*ccount-1;i_++)
ensemble.m_columnmeans[i_]=network.m_columnmeans[i_+i1_];
i1_=-(i*ccount);
for(i_=i*ccount;i_<=(i+1)*ccount-1;i_++)
ensemble.m_columnsigmas[i_]=network.m_columnsigmas[i_+i1_];
}
//--- serialized part
CMLPBase::MLPSerializeOld(network,ensemble.m_serializedmlp,ensemble.m_serializedlen);
//--- temporaries,internal buffers
ArrayResizeAL(ensemble.m_tmpweights,ensemble.m_wcount);
ArrayResizeAL(ensemble.m_tmpmeans,ccount);
ArrayResizeAL(ensemble.m_tmpsigmas,ccount);
ArrayResizeAL(ensemble.m_neurons,ensemble.m_structinfo[m_mlpntotaloffset]);
ArrayResizeAL(ensemble.m_dfdnet,ensemble.m_structinfo[m_mlpntotaloffset]);
ArrayResizeAL(ensemble.m_y,ensemble.m_nout);
}
//+------------------------------------------------------------------+
//| Copying of MLPEnsemble strucure |
//| INPUT PARAMETERS: |
//| Ensemble1 - original |
//| OUTPUT PARAMETERS: |
//| Ensemble2 - copy |
//+------------------------------------------------------------------+
static void CMLPE::MLPECopy(CMLPEnsemble &ensemble1,CMLPEnsemble &ensemble2)
{
//--- create variables
int i=0;
int ssize=0;
int ccount=0;
int ntotal=0;
int i_=0;
//--- Unload info
ssize=ensemble1.m_structinfo[0];
//--- check
if(ensemble1.m_issoftmax)
ccount=ensemble1.m_nin;
else
ccount=ensemble1.m_nin+ensemble1.m_nout;
//--- change value
ntotal=ensemble1.m_structinfo[m_mlpntotaloffset];
//--- Allocate space
ArrayResizeAL(ensemble2.m_structinfo,ssize);
ArrayResizeAL(ensemble2.m_weights,ensemble1.m_ensemblesize*ensemble1.m_wcount);
ArrayResizeAL(ensemble2.m_columnmeans,ensemble1.m_ensemblesize*ccount);
ArrayResizeAL(ensemble2.m_columnsigmas,ensemble1.m_ensemblesize*ccount);
ArrayResizeAL(ensemble2.m_tmpweights,ensemble1.m_wcount);
ArrayResizeAL(ensemble2.m_tmpmeans,ccount);
ArrayResizeAL(ensemble2.m_tmpsigmas,ccount);
ArrayResizeAL(ensemble2.m_serializedmlp,ensemble1.m_serializedlen);
ArrayResizeAL(ensemble2.m_neurons,ntotal);
ArrayResizeAL(ensemble2.m_dfdnet,ntotal);
ArrayResizeAL(ensemble2.m_y,ensemble1.m_nout);
//--- Copy
ensemble2.m_nin=ensemble1.m_nin;
ensemble2.m_nout=ensemble1.m_nout;
ensemble2.m_wcount=ensemble1.m_wcount;
ensemble2.m_ensemblesize=ensemble1.m_ensemblesize;
ensemble2.m_issoftmax=ensemble1.m_issoftmax;
ensemble2.m_postprocessing=ensemble1.m_postprocessing;
ensemble2.m_serializedlen=ensemble1.m_serializedlen;
//--- copy
for(i=0;i<=ssize-1;i++)
ensemble2.m_structinfo[i]=ensemble1.m_structinfo[i];
for(i_=0;i_<=ensemble1.m_ensemblesize*ensemble1.m_wcount-1;i_++)
ensemble2.m_weights[i_]=ensemble1.m_weights[i_];
for(i_=0;i_<=ensemble1.m_ensemblesize*ccount-1;i_++)
ensemble2.m_columnmeans[i_]=ensemble1.m_columnmeans[i_];
for(i_=0;i_<=ensemble1.m_ensemblesize*ccount-1;i_++)
ensemble2.m_columnsigmas[i_]=ensemble1.m_columnsigmas[i_];
for(i_=0;i_<=ensemble1.m_serializedlen-1;i_++)
ensemble2.m_serializedmlp[i_]=ensemble1.m_serializedmlp[i_];
}
//+------------------------------------------------------------------+
//| Serialization of MLPEnsemble strucure |
//| INPUT PARAMETERS: |
//| Ensemble- original |
//| OUTPUT PARAMETERS: |
//| RA - array of real numbers which stores ensemble, |
//| array[0..RLen-1] |
//| RLen - RA lenght |
//+------------------------------------------------------------------+
static void CMLPE::MLPESerialize(CMLPEnsemble &ensemble,double &ra[],int &rlen)
{
//--- create variables
int i=0;
int ssize=0;
int ntotal=0;
int ccount=0;
int hsize=0;
int offs=0;
int i_=0;
int i1_=0;
//--- initialization
rlen=0;
hsize=13;
ssize=ensemble.m_structinfo[0];
//--- check
if(ensemble.m_issoftmax)
ccount=ensemble.m_nin;
else
ccount=ensemble.m_nin+ensemble.m_nout;
//--- change values
ntotal=ensemble.m_structinfo[m_mlpntotaloffset];
rlen=hsize+ssize+ensemble.m_ensemblesize*ensemble.m_wcount+2*ccount*ensemble.m_ensemblesize+ensemble.m_serializedlen;
//--- RA format:
//--- [0] RLen
//--- [1] Version (MLPEVNum)
//--- [2] EnsembleSize
//--- [3] NIn
//--- [4] NOut
//--- [5] WCount
//--- [6] IsSoftmax 0/1
//--- [7] PostProcessing 0/1
//--- [8] sizeof(StructInfo)
//--- [9] NTotal (sizeof(Neurons),sizeof(DFDNET))
//--- [10] CCount (sizeof(ColumnMeans),sizeof(ColumnSigmas))
//--- [11] data offset
//--- [12] SerializedLen
//--- [..] StructInfo
//--- [..] Weights
//--- [..] ColumnMeans
//--- [..] ColumnSigmas
ArrayResizeAL(ra,rlen);
//--- change values
ra[0]=rlen;
ra[1]=m_mlpevnum;
ra[2]=ensemble.m_ensemblesize;
ra[3]=ensemble.m_nin;
ra[4]=ensemble.m_nout;
ra[5]=ensemble.m_wcount;
//--- check
if(ensemble.m_issoftmax)
ra[6]=1;
else
ra[6]=0;
//--- check
if(ensemble.m_postprocessing)
ra[7]=1;
else
ra[7]=9;
//--- change values
ra[8]=ssize;
ra[9]=ntotal;
ra[10]=ccount;
ra[11]=hsize;
ra[12]=ensemble.m_serializedlen;
//--- copy
offs=hsize;
for(i=offs;i<=offs+ssize-1;i++)
ra[i]=ensemble.m_structinfo[i-offs];
//--- copy
offs=offs+ssize;
i1_=-offs;
for(i_=offs;i_<=offs+ensemble.m_ensemblesize*ensemble.m_wcount-1;i_++)
ra[i_]=ensemble.m_weights[i_+i1_];
//--- copy
offs=offs+ensemble.m_ensemblesize*ensemble.m_wcount;
i1_=-offs;
for(i_=offs;i_<=offs+ensemble.m_ensemblesize*ccount-1;i_++)
ra[i_]=ensemble.m_columnmeans[i_+i1_];
//--- copy
offs=offs+ensemble.m_ensemblesize*ccount;
i1_=-offs;
for(i_=offs;i_<=offs+ensemble.m_ensemblesize*ccount-1;i_++)
ra[i_]=ensemble.m_columnsigmas[i_+i1_];
//--- copy
offs=offs+ensemble.m_ensemblesize*ccount;
i1_=-offs;
for(i_=offs;i_<=offs+ensemble.m_serializedlen-1;i_++)
ra[i_]=ensemble.m_serializedmlp[i_+i1_];
offs=offs+ensemble.m_serializedlen;
}
//+------------------------------------------------------------------+
//| Unserialization of MLPEnsemble strucure |
//| INPUT PARAMETERS: |
//| RA - real array which stores ensemble |
//| OUTPUT PARAMETERS: |
//| Ensemble- restored structure |
//+------------------------------------------------------------------+
static void CMLPE::MLPEUnserialize(double &ra[],CMLPEnsemble &ensemble)
{
//--- create variables
int i=0;
int ssize=0;
int ntotal=0;
int ccount=0;
int hsize=0;
int offs=0;
int i_=0;
int i1_=0;
//--- check
if(!CAp::Assert((int)MathRound(ra[1])==m_mlpevnum,__FUNCTION__+": incorrect array!"))
return;
//--- load info
hsize=13;
ensemble.m_ensemblesize=(int)MathRound(ra[2]);
ensemble.m_nin=(int)MathRound(ra[3]);
ensemble.m_nout=(int)MathRound(ra[4]);
ensemble.m_wcount=(int)MathRound(ra[5]);
ensemble.m_issoftmax=(int)MathRound(ra[6])==1;
ensemble.m_postprocessing=(int)MathRound(ra[7])==1;
ssize=(int)MathRound(ra[8]);
ntotal=(int)MathRound(ra[9]);
ccount=(int)MathRound(ra[10]);
offs=(int)MathRound(ra[11]);
ensemble.m_serializedlen=(int)MathRound(ra[12]);
//--- Allocate arrays
ArrayResizeAL(ensemble.m_structinfo,ssize);
ArrayResizeAL(ensemble.m_weights,ensemble.m_ensemblesize*ensemble.m_wcount);
ArrayResizeAL(ensemble.m_columnmeans,ensemble.m_ensemblesize*ccount);
ArrayResizeAL(ensemble.m_columnsigmas,ensemble.m_ensemblesize*ccount);
ArrayResizeAL(ensemble.m_tmpweights,ensemble.m_wcount);
ArrayResizeAL(ensemble.m_tmpmeans,ccount);
ArrayResizeAL(ensemble.m_tmpsigmas,ccount);
ArrayResizeAL(ensemble.m_neurons,ntotal);
ArrayResizeAL(ensemble.m_dfdnet,ntotal);
ArrayResizeAL(ensemble.m_serializedmlp,ensemble.m_serializedlen);
ArrayResizeAL(ensemble.m_y,ensemble.m_nout);
//--- load data
for(i=offs;i<=offs+ssize-1;i++)
ensemble.m_structinfo[i-offs]=(int)MathRound(ra[i]);
//--- copy
offs=offs+ssize;
i1_=offs;
for(i_=0;i_<=ensemble.m_ensemblesize*ensemble.m_wcount-1;i_++)
ensemble.m_weights[i_]=ra[i_+i1_];
//--- copy
offs=offs+ensemble.m_ensemblesize*ensemble.m_wcount;
i1_=offs;
for(i_=0;i_<=ensemble.m_ensemblesize*ccount-1;i_++)
ensemble.m_columnmeans[i_]=ra[i_+i1_];
//--- copy
offs=offs+ensemble.m_ensemblesize*ccount;
i1_=offs;
for(i_=0;i_<=ensemble.m_ensemblesize*ccount-1;i_++)
ensemble.m_columnsigmas[i_]=ra[i_+i1_];
//--- copy
offs=offs+ensemble.m_ensemblesize*ccount;
i1_=offs;
for(i_=0;i_<=ensemble.m_serializedlen-1;i_++)
ensemble.m_serializedmlp[i_]=ra[i_+i1_];
offs=offs+ensemble.m_serializedlen;
}
//+------------------------------------------------------------------+
//| Randomization of MLP ensemble |
//+------------------------------------------------------------------+
static void CMLPE::MLPERandomize(CMLPEnsemble &ensemble)
{
//--- create a variable
int i=0;
//--- calculation
for(i=0;i<=ensemble.m_ensemblesize*ensemble.m_wcount-1;i++)
ensemble.m_weights[i]=CMath::RandomReal()-0.5;
}
//+------------------------------------------------------------------+
//| Return ensemble properties (number of inputs and outputs). |
//+------------------------------------------------------------------+
static void CMLPE::MLPEProperties(CMLPEnsemble &ensemble,int &nin,int &nout)
{
//--- change values
nin=ensemble.m_nin;
nout=ensemble.m_nout;
}
//+------------------------------------------------------------------+
//| Return normalization type (whether ensemble is SOFTMAX-normalized|
//| or not). |
//+------------------------------------------------------------------+
static bool CMLPE::MLPEIsSoftMax(CMLPEnsemble &ensemble)
{
//--- return result
return(ensemble.m_issoftmax);
}
//+------------------------------------------------------------------+
//| Procesing |
//| INPUT PARAMETERS: |
//| Ensemble- neural networks ensemble |
//| X - input vector, array[0..NIn-1]. |
//| Y - (possibly) preallocated buffer; if size of Y is |
//| less than NOut, it will be reallocated. If it is |
//| large enough, it is NOT reallocated, so we can |
//| save some time on reallocation. |
//| OUTPUT PARAMETERS: |
//| Y - result. Regression estimate when solving |
//| regression task, vector of posterior |
//| probabilities for classification task. |
//+------------------------------------------------------------------+
static void CMLPE::MLPEProcess(CMLPEnsemble &ensemble,double &x[],double &y[])
{
//--- create variables
int i=0;
int es=0;
int wc=0;
int cc=0;
double v=0;
int i_=0;
int i1_=0;
//--- check
if(CAp::Len(y)<ensemble.m_nout)
ArrayResizeAL(y,ensemble.m_nout);
//--- initialization
es=ensemble.m_ensemblesize;
wc=ensemble.m_wcount;
//--- check
if(ensemble.m_issoftmax)
cc=ensemble.m_nin;
else
cc=ensemble.m_nin+ensemble.m_nout;
//--- initialization
v=1.0/(double)es;
for(i=0;i<=ensemble.m_nout-1;i++)
y[i]=0;
//--- calculation
for(i=0;i<=es-1;i++)
{
i1_=i*wc;
for(i_=0;i_<=wc-1;i_++)
ensemble.m_tmpweights[i_]=ensemble.m_weights[i_+i1_];
i1_=i*cc;
for(i_=0;i_<=cc-1;i_++)
ensemble.m_tmpmeans[i_]=ensemble.m_columnmeans[i_+i1_];
i1_=i*cc;
for(i_=0;i_<=cc-1;i_++)
ensemble.m_tmpsigmas[i_]=ensemble.m_columnsigmas[i_+i1_];
//--- function call
CMLPBase::MLPInternalProcessVector(ensemble.m_structinfo,ensemble.m_tmpweights,ensemble.m_tmpmeans,ensemble.m_tmpsigmas,ensemble.m_neurons,ensemble.m_dfdnet,x,ensemble.m_y);
//--- change values
for(i_=0;i_<=ensemble.m_nout-1;i_++)
y[i_]=y[i_]+v*ensemble.m_y[i_];
}
}
//+------------------------------------------------------------------+
//| 'interactive' variant of MLPEProcess for languages like Python |
//| which support constructs like "Y = MLPEProcess(LM,X)" and |
//| interactive mode of the interpreter |
//| This function allocates new array on each call, so it is |
//| significantly slower than its 'non-interactive' counterpart, but |
//| it is more convenient when you call it from command line. |
//+------------------------------------------------------------------+
static void CMLPE::MLPEProcessI(CMLPEnsemble &ensemble,double &x[],double &y[])
{
//--- function call
MLPEProcess(ensemble,x,y);
}
//+------------------------------------------------------------------+
//| Relative classification error on the test set |
//| INPUT PARAMETERS: |
//| Ensemble- ensemble |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| percent of incorrectly classified cases. |
//| Works both for classifier betwork and for regression networks|
//| which are used as classifiers. |
//+------------------------------------------------------------------+
static double CMLPE::MLPERelClsError(CMLPEnsemble &ensemble,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double relcls=0;
double avgce=0;
double rms=0;
double avg=0;
double avgrel=0;
//--- function call
MLPEAllErrors(ensemble,xy,npoints,relcls,avgce,rms,avg,avgrel);
//--- return result
return(relcls);
}
//+------------------------------------------------------------------+
//| Average cross-entropy (in bits per element) on the test set |
//| INPUT PARAMETERS: |
//| Ensemble- ensemble |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| CrossEntropy/(NPoints*LN(2)). |
//| Zero if ensemble solves regression task. |
//+------------------------------------------------------------------+
static double CMLPE::MLPEAvgCE(CMLPEnsemble &ensemble,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double relcls=0;
double avgce=0;
double rms=0;
double avg=0;
double avgrel=0;
//--- function call
MLPEAllErrors(ensemble,xy,npoints,relcls,avgce,rms,avg,avgrel);
//--- return result
return(avgce);
}
//+------------------------------------------------------------------+
//| RMS error on the test set |
//| INPUT PARAMETERS: |
//| Ensemble- ensemble |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| root mean square error. |
//| Its meaning for regression task is obvious. As for |
//| classification task RMS error means error when estimating |
//| posterior probabilities. |
//+------------------------------------------------------------------+
static double CMLPE::MLPERMSError(CMLPEnsemble &ensemble,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double relcls=0;
double avgce=0;
double rms=0;
double avg=0;
double avgrel=0;
//--- function call
MLPEAllErrors(ensemble,xy,npoints,relcls,avgce,rms,avg,avgrel);
//--- return result
return(rms);
}
//+------------------------------------------------------------------+
//| Average error on the test set |
//| INPUT PARAMETERS: |
//| Ensemble- ensemble |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| Its meaning for regression task is obvious. As for |
//| classification task it means average error when estimating |
//| posterior probabilities. |
//+------------------------------------------------------------------+
static double CMLPE::MLPEAvgError(CMLPEnsemble &ensemble,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double result=0;
double relcls=0;
double avgce=0;
double rms=0;
double avg=0;
double avgrel=0;
//--- function call
MLPEAllErrors(ensemble,xy,npoints,relcls,avgce,rms,avg,avgrel);
//--- return result
return(avg);
}
//+------------------------------------------------------------------+
//| Average relative error on the test set |
//| INPUT PARAMETERS: |
//| Ensemble- ensemble |
//| XY - test set |
//| NPoints - test set size |
//| RESULT: |
//| Its meaning for regression task is obvious. As for |
//| classification task it means average relative error when |
//| estimating posterior probabilities. |
//+------------------------------------------------------------------+
static double CMLPE::MLPEAvgRelError(CMLPEnsemble &ensemble,CMatrixDouble &xy,
const int npoints)
{
//--- create variables
double result=0;
double relcls=0;
double avgce=0;
double rms=0;
double avg=0;
double avgrel=0;
//--- function call
MLPEAllErrors(ensemble,xy,npoints,relcls,avgce,rms,avg,avgrel);
//--- return result
return(avgrel);
}
//+------------------------------------------------------------------+
//| Training neural networks ensemble using bootstrap aggregating |
//| (bagging). |
//| Modified Levenberg-Marquardt algorithm is used as base training |
//| method. |
//| INPUT PARAMETERS: |
//| Ensemble - model with initialized geometry |
//| XY - training set |
//| NPoints - training set size |
//| Decay - weight decay coefficient, >=0.001 |
//| Restarts - restarts, >0. |
//| OUTPUT PARAMETERS: |
//| Ensemble - trained model |
//| Info - return code: |
//| * -2, if there is a point with class number |
//| outside of [0..NClasses-1]. |
//| * -1, if incorrect parameters was passed |
//| (NPoints<0, Restarts<1). |
//| * 2, if task has been solved. |
//| Rep - training report. |
//| OOBErrors - out-of-bag generalization error estimate |
//+------------------------------------------------------------------+
static void CMLPE::MLPEBaggingLM(CMLPEnsemble &ensemble,CMatrixDouble &xy,
const int npoints,const double decay,
const int restarts,int &info,CMLPReport &rep,
CMLPCVReport &ooberrors)
{
//--- initialization
info=0;
//--- function call
MLPEBaggingInternal(ensemble,xy,npoints,decay,restarts,0.0,0,true,info,rep,ooberrors);
}
//+------------------------------------------------------------------+
//| Training neural networks ensemble using bootstrap aggregating |
//| (bagging). L-BFGS algorithm is used as base training method. |
//| INPUT PARAMETERS: |
//| Ensemble - model with initialized geometry |
//| XY - training set |
//| NPoints - training set size |
//| Decay - weight decay coefficient, >=0.001 |
//| Restarts - restarts, >0. |
//| WStep - stopping criterion, same as in MLPTrainLBFGS |
//| MaxIts - stopping criterion, same as in MLPTrainLBFGS |
//| OUTPUT PARAMETERS: |
//| Ensemble - trained model |
//| Info - return code: |
//| * -8, if both WStep=0 and MaxIts=0 |
//| * -2, if there is a point with class number |
//| outside of [0..NClasses-1]. |
//| * -1, if incorrect parameters was passed |
//| (NPoints<0, Restarts<1). |
//| * 2, if task has been solved. |
//| Rep - training report. |
//| OOBErrors - out-of-bag generalization error estimate |
//+------------------------------------------------------------------+
static void CMLPE::MLPEBaggingLBFGS(CMLPEnsemble &ensemble,CMatrixDouble &xy,
const int npoints,const double decay,
const int restarts,const double wstep,
const int maxits,int &info,CMLPReport &rep,
CMLPCVReport &ooberrors)
{
//--- initialization
info=0;
//--- function call
MLPEBaggingInternal(ensemble,xy,npoints,decay,restarts,wstep,maxits,false,info,rep,ooberrors);
}
//+------------------------------------------------------------------+
//| Training neural networks ensemble using early stopping. |
//| INPUT PARAMETERS: |
//| Ensemble - model with initialized geometry |
//| XY - training set |
//| NPoints - training set size |
//| Decay - weight decay coefficient, >=0.001 |
//| Restarts - restarts, >0. |
//| OUTPUT PARAMETERS: |
//| Ensemble - trained model |
//| Info - return code: |
//| * -2, if there is a point with class number |
//| outside of [0..NClasses-1]. |
//| * -1, if incorrect parameters was passed |
//| (NPoints<0, Restarts<1). |
//| * 6, if task has been solved. |
//| Rep - training report. |
//| OOBErrors - out-of-bag generalization error estimate |
//+------------------------------------------------------------------+
static void CMLPE::MLPETrainES(CMLPEnsemble &ensemble,CMatrixDouble &xy,
const int npoints,const double decay,
const int restarts,int &info,CMLPReport &rep)
{
//--- create variables
int i=0;
int k=0;
int ccount=0;
int pcount=0;
int trnsize=0;
int valsize=0;
int tmpinfo=0;
int i_=0;
int i1_=0;
//--- create matrix
CMatrixDouble trnxy;
CMatrixDouble valxy;
//--- objects of classes
CMultilayerPerceptron network;
CMLPReport tmprep;
//--- initialization
info=0;
//--- check
if((npoints<2 || restarts<1) || decay<0.0)
{
info=-1;
return;
}
//--- check
if(ensemble.m_issoftmax)
{
for(i=0;i<=npoints-1;i++)
{
//--- check
if((int)MathRound(xy[i][ensemble.m_nin])<0 || (int)MathRound(xy[i][ensemble.m_nin])>=ensemble.m_nout)
{
info=-2;
return;
}
}
}
//--- change value
info=6;
//--- allocate
if(ensemble.m_issoftmax)
{
ccount=ensemble.m_nin+1;
pcount=ensemble.m_nin;
}
else
{
ccount=ensemble.m_nin+ensemble.m_nout;
pcount=ensemble.m_nin+ensemble.m_nout;
}
//--- allocation
trnxy.Resize(npoints,ccount);
valxy.Resize(npoints,ccount);
//--- function call
CMLPBase::MLPUnserializeOld(ensemble.m_serializedmlp,network);
//--- change values
rep.m_ngrad=0;
rep.m_nhess=0;
rep.m_ncholesky=0;
//--- train networks
for(k=0;k<=ensemble.m_ensemblesize-1;k++)
{
//--- Split set
do
{
trnsize=0;
valsize=0;
for(i=0;i<=npoints-1;i++)
{
//--- check
if(CMath::RandomReal()<0.66)
{
//--- Assign sample to training set
for(i_=0;i_<=ccount-1;i_++)
trnxy[trnsize].Set(i_,xy[i][i_]);
trnsize=trnsize+1;
}
else
{
//--- Assign sample to validation set
for(i_=0;i_<=ccount-1;i_++)
valxy[valsize].Set(i_,xy[i][i_]);
valsize=valsize+1;
}
}
}
while(!(trnsize!=0 && valsize!=0));
//--- Train
CMLPTrain::MLPTrainES(network,trnxy,trnsize,valxy,valsize,decay,restarts,tmpinfo,tmprep);
//--- check
if(tmpinfo<0)
{
info=tmpinfo;
return;
}
//--- save results
i1_=-(k*ensemble.m_wcount);
for(i_=k*ensemble.m_wcount;i_<=(k+1)*ensemble.m_wcount-1;i_++)
ensemble.m_weights[i_]=network.m_weights[i_+i1_];
i1_=-(k*pcount);
for(i_=k*pcount;i_<=(k+1)*pcount-1;i_++)
ensemble.m_columnmeans[i_]=network.m_columnmeans[i_+i1_];
i1_=-(k*pcount);
for(i_=k*pcount;i_<=(k+1)*pcount-1;i_++)
ensemble.m_columnsigmas[i_]=network.m_columnsigmas[i_+i1_];
//--- change values
rep.m_ngrad=rep.m_ngrad+tmprep.m_ngrad;
rep.m_nhess=rep.m_nhess+tmprep.m_nhess;
rep.m_ncholesky=rep.m_ncholesky+tmprep.m_ncholesky;
}
}
//+------------------------------------------------------------------+
//| Calculation of all types of errors |
//+------------------------------------------------------------------+
static void CMLPE::MLPEAllErrors(CMLPEnsemble &ensemble,CMatrixDouble &xy,
const int npoints,double &relcls,
double &avgce,double &rms,
double &avg,double &avgrel)
{
//--- create variables
int i=0;
int i_=0;
int i1_=0;
//--- creating arrays
double buf[];
double workx[];
double y[];
double dy[];
//--- initialization
relcls=0;
avgce=0;
rms=0;
avg=0;
avgrel=0;
//--- allocation
ArrayResizeAL(workx,ensemble.m_nin);
ArrayResizeAL(y,ensemble.m_nout);
//--- check
if(ensemble.m_issoftmax)
{
//--- allocation
ArrayResizeAL(dy,1);
//--- function call
CBdSS::DSErrAllocate(ensemble.m_nout,buf);
}
else
{
//--- allocation
ArrayResizeAL(dy,ensemble.m_nout);
//--- function call
CBdSS::DSErrAllocate(-ensemble.m_nout,buf);
}
//--- calculation
for(i=0;i<=npoints-1;i++)
{
for(i_=0;i_<=ensemble.m_nin-1;i_++)
workx[i_]=xy[i][i_];
//--- function call
MLPEProcess(ensemble,workx,y);
//--- check
if(ensemble.m_issoftmax)
dy[0]=xy[i][ensemble.m_nin];
else
{
i1_=ensemble.m_nin;
for(i_=0;i_<=ensemble.m_nout-1;i_++)
dy[i_]=xy[i][i_+i1_];
}
//--- function call
CBdSS::DSErrAccumulate(buf,y,dy);
}
//--- function call
CBdSS::DSErrFinish(buf);
//--- change values
relcls=buf[0];
avgce=buf[1];
rms=buf[2];
avg=buf[3];
avgrel=buf[4];
}
//+------------------------------------------------------------------+
//| Internal bagging subroutine. |
//+------------------------------------------------------------------+
static void CMLPE::MLPEBaggingInternal(CMLPEnsemble &ensemble,CMatrixDouble &xy,
const int npoints,const double decay,
const int restarts,const double wstep,
const int maxits,const bool lmalgorithm,
int &info,CMLPReport &rep,CMLPCVReport &ooberrors)
{
//--- create variables
int nin=0;
int nout=0;
int ccnt=0;
int pcnt=0;
int i=0;
int j=0;
int k=0;
double v=0;
int i_=0;
int i1_=0;
//--- creating arrays
bool s[];
int oobcntbuf[];
double x[];
double y[];
double dy[];
double dsbuf[];
//--- create matrix
CMatrixDouble xys;
CMatrixDouble oobbuf;
//--- objects of classes
CMLPReport tmprep;
CMultilayerPerceptron network;
//--- initialization
info=0;
//--- Test for inputs
if((!lmalgorithm && wstep==0.0) && maxits==0)
{
info=-8;
return;
}
//--- check
if(((npoints<=0 || restarts<1) || wstep<0.0) || maxits<0)
{
info=-1;
return;
}
//--- check
if(ensemble.m_issoftmax)
{
for(i=0;i<=npoints-1;i++)
{
//--- check
if((int)MathRound(xy[i][ensemble.m_nin])<0 || (int)MathRound(xy[i][ensemble.m_nin])>=ensemble.m_nout)
{
info=-2;
return;
}
}
}
//--- allocate temporaries
info=2;
rep.m_ngrad=0;
rep.m_nhess=0;
rep.m_ncholesky=0;
ooberrors.m_relclserror=0;
ooberrors.m_avgce=0;
ooberrors.m_rmserror=0;
ooberrors.m_avgerror=0;
ooberrors.m_avgrelerror=0;
nin=ensemble.m_nin;
nout=ensemble.m_nout;
//--- check
if(ensemble.m_issoftmax)
{
ccnt=nin+1;
pcnt=nin;
}
else
{
ccnt=nin+nout;
pcnt=nin+nout;
}
//--- allocation
xys.Resize(npoints,ccnt);
ArrayResizeAL(s,npoints);
oobbuf.Resize(npoints,nout);
ArrayResizeAL(oobcntbuf,npoints);
ArrayResizeAL(x,nin);
ArrayResizeAL(y,nout);
//--- check
if(ensemble.m_issoftmax)
ArrayResizeAL(dy,1);
else
ArrayResizeAL(dy,nout);
//--- initialization
for(i=0;i<=npoints-1;i++)
{
for(j=0;j<=nout-1;j++)
oobbuf[i].Set(j,0);
}
for(i=0;i<=npoints-1;i++)
oobcntbuf[i]=0;
//--- function call
CMLPBase::MLPUnserializeOld(ensemble.m_serializedmlp,network);
//--- main bagging cycle
for(k=0;k<=ensemble.m_ensemblesize-1;k++)
{
//--- prepare dataset
for(i=0;i<=npoints-1;i++)
s[i]=false;
for(i=0;i<=npoints-1;i++)
{
j=CMath::RandomInteger(npoints);
s[j]=true;
for(i_=0;i_<=ccnt-1;i_++)
xys[i].Set(i_,xy[j][i_]);
}
//--- train
if(lmalgorithm)
CMLPTrain::MLPTrainLM(network,xys,npoints,decay,restarts,info,tmprep);
else
CMLPTrain::MLPTrainLBFGS(network,xys,npoints,decay,restarts,wstep,maxits,info,tmprep);
//--- check
if(info<0)
return;
//--- save results
rep.m_ngrad=rep.m_ngrad+tmprep.m_ngrad;
rep.m_nhess=rep.m_nhess+tmprep.m_nhess;
rep.m_ncholesky=rep.m_ncholesky+tmprep.m_ncholesky;
//--- copy
i1_=-(k*ensemble.m_wcount);
for(i_=k*ensemble.m_wcount;i_<=(k+1)*ensemble.m_wcount-1;i_++)
ensemble.m_weights[i_]=network.m_weights[i_+i1_];
//--- copy
i1_=-(k*pcnt);
for(i_=k*pcnt;i_<=(k+1)*pcnt-1;i_++)
ensemble.m_columnmeans[i_]=network.m_columnmeans[i_+i1_];
//--- copy
i1_=-(k*pcnt);
for(i_=k*pcnt;i_<=(k+1)*pcnt-1;i_++)
ensemble.m_columnsigmas[i_]=network.m_columnsigmas[i_+i1_];
//--- OOB estimates
for(i=0;i<=npoints-1;i++)
{
//--- check
if(!s[i])
{
for(i_=0;i_<=nin-1;i_++)
x[i_]=xy[i][i_];
//--- function call
CMLPBase::MLPProcess(network,x,y);
//--- change value
for(i_=0;i_<=nout-1;i_++)
oobbuf[i].Set(i_,oobbuf[i][i_]+y[i_]);
oobcntbuf[i]=oobcntbuf[i]+1;
}
}
}
//--- OOB estimates
if(ensemble.m_issoftmax)
{
//--- function call
CBdSS::DSErrAllocate(nout,dsbuf);
}
else
{
//--- function call
CBdSS::DSErrAllocate(-nout,dsbuf);
}
for(i=0;i<=npoints-1;i++)
{
//--- check
if(oobcntbuf[i]!=0)
{
v=1.0/(double)oobcntbuf[i];
for(i_=0;i_<=nout-1;i_++)
y[i_]=v*oobbuf[i][i_];
//--- check
if(ensemble.m_issoftmax)
dy[0]=xy[i][nin];
else
{
i1_=nin;
for(i_=0;i_<=nout-1;i_++)
dy[i_]=v*xy[i][i_+i1_];
}
//--- function call
CBdSS::DSErrAccumulate(dsbuf,y,dy);
}
}
//--- function call
CBdSS::DSErrFinish(dsbuf);
//--- change values
ooberrors.m_relclserror=dsbuf[0];
ooberrors.m_avgce=dsbuf[1];
ooberrors.m_rmserror=dsbuf[2];
ooberrors.m_avgerror=dsbuf[3];
ooberrors.m_avgrelerror=dsbuf[4];
}
//+------------------------------------------------------------------+
//| Principal components analysis |
//+------------------------------------------------------------------+
class CPCAnalysis
{
public:
//--- constructor, destructor
CPCAnalysis(void);
~CPCAnalysis(void);
//--- method
static void PCABuildBasis(CMatrixDouble &x,const int npoints,const int nvars,int &info,double &s2[],CMatrixDouble &v);
};
//+------------------------------------------------------------------+
//| Constructor without parameters |
//+------------------------------------------------------------------+
CPCAnalysis::CPCAnalysis(void)
{
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CPCAnalysis::~CPCAnalysis(void)
{
}
//+------------------------------------------------------------------+
//| Principal components analysis |
//| Subroutine builds orthogonal basis where first axis corresponds |
//| to direction with maximum variance, second axis maximizes |
//| variance in subspace orthogonal to first axis and so on. |
//| It should be noted that, unlike LDA, PCA does not use class |
//| labels. |
//| INPUT PARAMETERS: |
//| X - dataset, array[0..NPoints-1,0..NVars-1]. |
//| matrix contains ONLY INDEPENDENT VARIABLES. |
//| NPoints - dataset size, NPoints>=0 |
//| NVars - number of independent variables, NVars>=1 |
//| OUTPUT PARAMETERS: |
//| Info - return code: |
//| * -4, if SVD subroutine haven't converged |
//| * -1, if wrong parameters has been passed |
//| (NPoints<0, NVars<1) |
//| * 1, if task is solved |
//| S2 - array[0..NVars-1]. variance values |
//| corresponding to basis vectors. |
//| V - array[0..NVars-1,0..NVars-1] |
//| matrix, whose columns store basis vectors. |
//+------------------------------------------------------------------+
static void CPCAnalysis::PCABuildBasis(CMatrixDouble &x,const int npoints,
const int nvars,int &info,double &s2[],
CMatrixDouble &v)
{
//--- create variables
int i=0;
int j=0;
double mean=0;
double variance=0;
double skewness=0;
double kurtosis=0;
int i_=0;
//--- creating arrays
double m[];
double t[];
//--- create matrix
CMatrixDouble a;
CMatrixDouble u;
CMatrixDouble vt;
//--- initialization
info=0;
//--- Check input data
if(npoints<0 || nvars<1)
{
info=-1;
return;
}
//--- change value
info=1;
//--- Special case: NPoints=0
if(npoints==0)
{
//--- allocation
ArrayResizeAL(s2,nvars);
v.Resize(nvars,nvars);
//--- initialization
for(i=0;i<=nvars-1;i++)
s2[i]=0;
for(i=0;i<=nvars-1;i++)
{
for(j=0;j<=nvars-1;j++)
{
//--- check
if(i==j)
v[i].Set(j,1);
else
v[i].Set(j,0);
}
}
//--- exit the function
return;
}
//--- Calculate means
ArrayResizeAL(m,nvars);
ArrayResizeAL(t,npoints);
for(j=0;j<=nvars-1;j++)
{
for(i_=0;i_<=npoints-1;i_++)
t[i_]=x[i_][j];
//--- function call
CBaseStat::SampleMoments(t,npoints,mean,variance,skewness,kurtosis);
m[j]=mean;
}
//--- Center,apply SVD,prepare output
a.Resize(MathMax(npoints,nvars),nvars);
//--- calculation
for(i=0;i<=npoints-1;i++)
{
for(i_=0;i_<=nvars-1;i_++)
a[i].Set(i_,x[i][i_]);
for(i_=0;i_<=nvars-1;i_++)
a[i].Set(i_,a[i][i_]-m[i_]);
}
for(i=npoints;i<=nvars-1;i++)
{
for(j=0;j<=nvars-1;j++)
a[i].Set(j,0);
}
//--- check
if(!CSingValueDecompose::RMatrixSVD(a,MathMax(npoints,nvars),nvars,0,1,2,s2,u,vt))
{
info=-4;
return;
}
//--- check
if(npoints!=1)
{
for(i=0;i<=nvars-1;i++)
s2[i]=CMath::Sqr(s2[i])/(npoints-1);
}
//--- allocation
v.Resize(nvars,nvars);
//--- function call
CBlas::CopyAndTranspose(vt,0,nvars-1,0,nvars-1,v,0,nvars-1,0,nvars-1);
}
//+------------------------------------------------------------------+