26388 lines
1.1 MiB
Plaintext
26388 lines
1.1 MiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| dataanalysis.mqh |
|
|
//| Copyright 2003-2022 Sergey Bochkanov (ALGLIB project) |
|
|
//| Copyright 2012-2026, MetaQuotes Ltd. |
|
|
//| 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 |
|
|
//+------------------------------------------------------------------+
|
|
struct CCVReport
|
|
{
|
|
double m_RelCLSError;
|
|
double m_AvgCE;
|
|
double m_RMSError;
|
|
double m_AvgError;
|
|
double m_AvgRelError;
|
|
//---
|
|
CCVReport(void) { ZeroMemory(this); }
|
|
~CCVReport(void) {}
|
|
//---
|
|
void Copy(const CCVReport &obj);
|
|
//--- overloading
|
|
void operator=(const CCVReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CCVReport::Copy(const CCVReport &obj)
|
|
{
|
|
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;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Data analysis |
|
|
//+------------------------------------------------------------------+
|
|
class CBdSS
|
|
{
|
|
public:
|
|
static void DSErrAllocate(const int nclasses,double &buf[]);
|
|
static void DSErrAllocate(const int nclasses,CRowDouble &buf);
|
|
static void DSErrAccumulate(double &buf[],double &y[],double &desiredy[]);
|
|
static void DSErrAccumulate(CRowDouble &buf,CRowDouble &y,CRowDouble &desiredy);
|
|
static void DSErrFinish(double &buf[]);
|
|
static void DSErrFinish(CRowDouble &buf);
|
|
static void DSNormalize(CMatrixDouble &xy,const int npoints,const int nvars,int &info,double &means[],double &sigmas[]);
|
|
static void DSNormalize(CMatrixDouble &xy,const int npoints,const int nvars,int &info,CRowDouble &means,CRowDouble &sigmas);
|
|
static void DSNormalizeC(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,CRowDouble &means,CRowDouble &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 DSTie(CRowDouble &a,const int n,CRowInt &ties,int &tiecount,CRowInt &p1,CRowInt &p2);
|
|
static void DSTieFastI(double &a[],int &b[],const int n,int &ties[],int &tiecount,double &bufr[],int &bufi[]);
|
|
static void DSTieFastI(CRowDouble &a,CRowInt &b,const int n,CRowInt &ties,int &tiecount,CRowDouble &bufr,CRowInt &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 DSOptimalSplit2(CRowDouble &ca,CRowInt &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 DSOptimalSplit2Fast(CRowDouble &a,CRowInt &c,CRowInt &tiesbuf,CRowInt &cntbuf,CRowDouble &bufr,CRowInt &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 DSSplitK(CRowDouble &ca,CRowInt &cc,const int n,const int nc,int kmax,int &info,CRowDouble &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);
|
|
static void DSOptimalSplitK(CRowDouble &ca,CRowInt &cc,const int n,const int nc,int kmax,int &info,CRowDouble &thresholds,int &ni,double &cve);
|
|
|
|
private:
|
|
static double XLnY(const double x,const double y);
|
|
static double GetCV(CRowInt &cnt,const int nc);
|
|
static void TieAddC(CRowInt &c,CRowInt &ties,const int ntie,const int nc,CRowInt &cnt);
|
|
static void TieSubC(CRowInt &c,CRowInt &ties,const int ntie,const int nc,CRowInt &cnt);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSErrAllocate(const int nclasses,double &buf[])
|
|
{
|
|
CRowDouble Buf;
|
|
DSErrAllocate(nclasses,Buf);
|
|
Buf.ToArray(buf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSErrAllocate(const int nclasses,CRowDouble &buf)
|
|
{
|
|
//--- allocation
|
|
buf=vector<double>::Zeros(8);
|
|
//--- initialization
|
|
buf.Set(5,(double)nclasses);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| See DSErrAllocate for comments on this routine. |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSErrAccumulate(double &buf[],double &y[],double &desiredy[])
|
|
{
|
|
CRowDouble Buf=buf;
|
|
CRowDouble Y=y;
|
|
CRowDouble DesiredY=desiredy;
|
|
DSErrAccumulate(Buf,Y,DesiredY);
|
|
Buf.ToArray(buf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| See DSErrAllocate for comments on this routine. |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSErrAccumulate(CRowDouble &buf,CRowDouble &y,CRowDouble &desiredy)
|
|
{
|
|
//--- create variables
|
|
int offs=5;
|
|
int nclasses=(int)MathRound(buf[offs]);
|
|
int nout=0;
|
|
int mmax=0;
|
|
int rmax=0;
|
|
int j=0;
|
|
double v=0;
|
|
//--- initialization
|
|
vector<double> ev=vector<double>::Zeros(MathAbs(nclasses));
|
|
//--- check
|
|
if(nclasses>0)
|
|
{
|
|
//--- Classification
|
|
rmax=(int)MathRound(desiredy[0]);
|
|
//--- initialization
|
|
mmax=(int)y.ArgMax();
|
|
//--- check
|
|
if(mmax!=rmax)
|
|
buf.Add(0,1);
|
|
//--- check
|
|
if(y[rmax]>0.0)
|
|
buf.Add(1,-MathLog(y[rmax]));
|
|
else
|
|
buf.Add(1,MathLog(CMath::m_maxrealnumber));
|
|
//--- calculation
|
|
if(rmax>=0 && rmax<nclasses)
|
|
{
|
|
buf.Add(4,MathAbs(1-y[rmax]));
|
|
buf.Add(offs+2,1);
|
|
ev[rmax]=1;
|
|
}
|
|
vector<double> err=y.ToVector()-ev;
|
|
buf.Add(2,MathPow(err,2.0).Sum());
|
|
buf.Add(3,MathAbs(err).Sum());
|
|
//--- change value
|
|
buf.Add(offs+1,1);
|
|
}
|
|
else
|
|
{
|
|
//--- Regression
|
|
nout=-nclasses;
|
|
//--- initialization
|
|
rmax=(int)desiredy.ArgMax();
|
|
//--- initialization
|
|
mmax=(int)y.ArgMax();
|
|
//--- check
|
|
if(mmax!=rmax)
|
|
buf.Add(0,1);
|
|
//--- calculation
|
|
ev=desiredy.ToVector();
|
|
vector<double> err=y.ToVector()-ev;
|
|
buf.Add(2,MathPow(err,2.0).Sum());
|
|
buf.Add(3,MathAbs(err).Sum());
|
|
for(j=0; j<nout; j++)
|
|
{
|
|
//--- check
|
|
if(ev[j]!=0.0)
|
|
{
|
|
buf.Add(4,MathAbs(err[j]/ev[j]));
|
|
buf.Add(offs+2,1);
|
|
}
|
|
}
|
|
//--- change value
|
|
buf.Add(offs+1,1);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| See DSErrAllocate for comments on this routine. |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSErrFinish(double &buf[])
|
|
{
|
|
CRowDouble Buf=buf;
|
|
DSErrFinish(Buf);
|
|
Buf.ToArray(buf);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSErrFinish(CRowDouble &buf)
|
|
{
|
|
//--- create variables
|
|
int offs=5;
|
|
int nout=MathAbs((int)MathRound(buf[offs]));
|
|
//--- check
|
|
if(buf[offs+1]!=0.0)
|
|
{
|
|
//--- change values
|
|
buf.Mul(0,1.0/buf[offs+1]);
|
|
buf.Mul(1,1.0/buf[offs+1]);
|
|
buf.Set(2,MathSqrt(buf[2]/(nout*buf[offs+1])));
|
|
buf.Mul(3,1.0/(nout*buf[offs+1]));
|
|
}
|
|
//--- check
|
|
if(buf[offs+2]!=0.0)
|
|
buf.Mul(4,1.0/buf[offs+2]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Normalize |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSNormalize(CMatrixDouble &xy,const int npoints,
|
|
const int nvars,int &info,double &means[],
|
|
double &sigmas[])
|
|
{
|
|
CRowDouble Means,Sigmas;
|
|
DSNormalize(xy,npoints,nvars,info,Means,Sigmas);
|
|
Means.ToArray(means);
|
|
Sigmas.ToArray(sigmas);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSNormalize(CMatrixDouble &xy,const int npoints,
|
|
const int nvars,int &info,CRowDouble &means,
|
|
CRowDouble &sigmas)
|
|
{
|
|
//--- function call
|
|
DSNormalizeC(xy,npoints,nvars,info,means,sigmas);
|
|
//--- calculation
|
|
for(int j=0; j<nvars; j++)
|
|
{
|
|
//--- change values
|
|
for(int i=0; i<npoints; i++)
|
|
xy.Set(i,j,(xy.Get(i,j)-means[j])/sigmas[j]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Normalize |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSNormalizeC(CMatrixDouble &xy,const int npoints,
|
|
const int nvars,int &info,double &means[],
|
|
double &sigmas[])
|
|
{
|
|
CRowDouble Means,Sigmas;
|
|
DSNormalizeC(xy,npoints,nvars,info,Means,Sigmas);
|
|
Means.ToArray(means);
|
|
Sigmas.ToArray(sigmas);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Normalize |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSNormalizeC(CMatrixDouble &xy,const int npoints,
|
|
const int nvars,int &info,CRowDouble &means,
|
|
CRowDouble &sigmas)
|
|
{
|
|
//--- initialization
|
|
info=0;
|
|
//--- Test parameters
|
|
if(npoints<=0 || nvars<1)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
//--- copy
|
|
matrix<double> tmp=xy.ToMatrix();
|
|
tmp.Resize(npoints,nvars);
|
|
//--- change value
|
|
info=1;
|
|
//--- Standartization
|
|
means=tmp.Mean(0);
|
|
sigmas=tmp.Std(0);
|
|
//--- calculation
|
|
for(int j=0; j<nvars; j++)
|
|
if(sigmas[j]==0.0)
|
|
sigmas.Set(j,1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Method |
|
|
//+------------------------------------------------------------------+
|
|
double CBdSS::DSGetMeanMindIstance(CMatrixDouble &xy,const int npoints,
|
|
const int nvars)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
double v=0;
|
|
//--- creating arrays
|
|
vector<double> tmp;
|
|
vector<double> tmp2;
|
|
//--- Test parameters
|
|
if(npoints<=0 || nvars<1)
|
|
return(0);
|
|
//--- Process
|
|
tmp=vector<double>::Full(npoints,CMath::m_maxrealnumber);
|
|
//--- allocation
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
for(int j=i+1; j<npoints; j++)
|
|
{
|
|
//--- calculation
|
|
tmp2=xy[i];
|
|
tmp2.Resize(nvars);
|
|
for(int i_=0; i_<nvars; i_++)
|
|
tmp2[i_]-=xy.Get(j,i_);
|
|
v=MathSqrt(MathPow(tmp2,2).Sum());
|
|
//--- change values
|
|
tmp[i]=MathMin(tmp[i],v);
|
|
tmp[j]=MathMin(tmp[j],v);
|
|
}
|
|
}
|
|
//--- get result
|
|
result=tmp.Sum()/npoints;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Method |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSTie(double &a[],const int n,int &ties[],int &tiecount,
|
|
int &p1[],int &p2[])
|
|
{
|
|
CRowDouble A=a;
|
|
CRowInt Ties=ties;
|
|
CRowInt P1=p1;
|
|
CRowInt P2=p2;
|
|
DSTie(A,n,Ties,tiecount,P1,P2);
|
|
A.ToArray(a);
|
|
Ties.ToArray(ties);
|
|
P1.ToArray(p1);
|
|
P2.ToArray(p2);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSTie(CRowDouble &a,const int n,CRowInt &ties,int &tiecount,
|
|
CRowInt &p1,CRowInt &p2)
|
|
{
|
|
//--- initialization
|
|
tiecount=0;
|
|
//--- Special case
|
|
if(n<=0)
|
|
{
|
|
tiecount=0;
|
|
return;
|
|
}
|
|
//--- Sort A
|
|
CTSort::TagSort(a,n,p1,p2);
|
|
//--- Process ties
|
|
//--- allocation
|
|
ties.Resize(n+1);
|
|
//--- change values
|
|
ties.Set(0,0);
|
|
tiecount=1;
|
|
for(int i=1; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(a[i]!=a[i-1])
|
|
{
|
|
ties.Set(tiecount,i);
|
|
tiecount++;
|
|
}
|
|
}
|
|
//--- change value
|
|
ties.Set(tiecount,n);
|
|
ties.Resize(tiecount+1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSTieFastI(double &a[],int &b[],const int n,int &ties[],
|
|
int &tiecount,double &bufr[],int &bufi[])
|
|
{
|
|
CRowDouble A=a;
|
|
CRowInt B=b;
|
|
CRowInt Ties=ties;
|
|
CRowDouble BufR=bufr;
|
|
CRowInt BufI=bufi;
|
|
DSTieFastI(A,B,n,Ties,tiecount,BufR,BufI);
|
|
A.ToArray(a);
|
|
B.ToArray(b);
|
|
Ties.ToArray(ties);
|
|
BufR.ToArray(bufr);
|
|
BufI.ToArray(bufi);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSTieFastI(CRowDouble &a,CRowInt &b,const int n,CRowInt &ties,
|
|
int &tiecount,CRowDouble &bufr,CRowInt &bufi)
|
|
{
|
|
//--- Special case
|
|
if(n<=0)
|
|
{
|
|
tiecount=0;
|
|
return;
|
|
}
|
|
//--- Sort A
|
|
CTSort::TagSortFastI(a,b,bufr,bufi,n);
|
|
//--- Process ties
|
|
ties.Set(0,0);
|
|
int k=1;
|
|
//--- calculation
|
|
for(int i=1; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(a[i]!=a[i-1])
|
|
{
|
|
ties.Set(k,i);
|
|
k++;
|
|
}
|
|
}
|
|
//--- change values
|
|
ties.Set(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 |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSOptimalSplit2(double &ca[],int &cc[],const int n,
|
|
int &info,double &threshold,double &pal,
|
|
double &pbl,double &par,double &pbr,
|
|
double &cve)
|
|
{
|
|
CRowDouble A=ca;
|
|
CRowInt C=cc;
|
|
DSOptimalSplit2(A,C,n,info,threshold,pal,pbl,par,pbr,cve);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSOptimalSplit2(CRowDouble &ca,CRowInt &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
|
|
CRowInt ties;
|
|
CRowInt p1;
|
|
CRowInt p2;
|
|
//--- copy
|
|
CRowDouble a=ca;
|
|
CRowInt 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; 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; i++)
|
|
{
|
|
//--- check
|
|
if(p2[i]!=i)
|
|
c.Swap(i,p2[i]);
|
|
}
|
|
//--- Special case: number of ties is 1.
|
|
//--- NOTE: we assume that P.Get(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.Get(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; 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-1; 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]; 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+=pak;
|
|
pbl+=pbk;
|
|
par-=pak;
|
|
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; 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. |
|
|
//+------------------------------------------------------------------+
|
|
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)
|
|
{
|
|
CRowDouble A=a;
|
|
CRowInt C=c;
|
|
CRowInt TiesBuf=tiesbuf;
|
|
CRowInt CntBuf=cntbuf;
|
|
CRowDouble BufR=bufr;
|
|
CRowInt BufI=bufi;
|
|
DSOptimalSplit2Fast(A,C,TiesBuf,CntBuf,BufR,BufI,n,nc,alpha,info,threshold,rms,cvrms);
|
|
A.ToArray(a);
|
|
C.ToArray(c);
|
|
TiesBuf.ToArray(tiesbuf);
|
|
CntBuf.ToArray(cntbuf);
|
|
BufR.ToArray(bufr);
|
|
BufI.ToArray(bufi);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSOptimalSplit2Fast(CRowDouble &a,CRowInt &c,CRowInt &tiesbuf,
|
|
CRowInt &cntbuf,CRowDouble &bufr,CRowInt &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; 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
|
|
cntbuf.Fill(0,0,2*nc);
|
|
for(i=0; i<n; i++)
|
|
cntbuf.Add(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-1; k++)
|
|
{
|
|
//--- first,move Kth tie from right to left
|
|
for(i=tiesbuf[k]; i<tiesbuf[k+1]; i++)
|
|
{
|
|
cl=c[i];
|
|
cntbuf.Add(cl,1);
|
|
cntbuf.Add(nc+cl,-1);
|
|
}
|
|
sl+=(tiesbuf[k+1]-tiesbuf[k]);
|
|
sr-=(tiesbuf[k+1]-tiesbuf[k]);
|
|
//--- Calculate RMS error
|
|
v=0;
|
|
for(i=0; i<nc; i++)
|
|
{
|
|
w=cntbuf[i];
|
|
v+=w*CMath::Sqr(w/sl-1);
|
|
v+=(sl-w)*CMath::Sqr(w/sl);
|
|
w=cntbuf[nc+i];
|
|
v+=w*CMath::Sqr(w/sr-1);
|
|
v+=(sr-w)*CMath::Sqr(w/sr);
|
|
}
|
|
//--- change value
|
|
v=MathSqrt(v/(nc*n));
|
|
//--- Compare with best
|
|
x=(2.0*sl)/(double)(sl+sr)-1.0;
|
|
cc=v*(1.0+alpha*(CMath::Sqr(x)-1.0));
|
|
//--- check
|
|
if(cc<cbest)
|
|
{
|
|
//--- store split
|
|
rms=v;
|
|
koptimal=k;
|
|
cbest=cc;
|
|
//--- calculate CVRMS error
|
|
cvrms=0;
|
|
for(i=0; i<nc; i++)
|
|
{
|
|
//--- check
|
|
if(sl>1)
|
|
{
|
|
w=cntbuf[i];
|
|
cvrms+=w*CMath::Sqr((w-1.0)/(sl-1.0)-1.0);
|
|
cvrms+=(sl-w)*CMath::Sqr(w/(sl-1.0));
|
|
}
|
|
else
|
|
{
|
|
w=cntbuf[i];
|
|
cvrms+=w*CMath::Sqr(1.0/(double)nc-1.0);
|
|
cvrms+=(sl-w)*CMath::Sqr(1.0/(double)nc);
|
|
}
|
|
//--- check
|
|
if(sr>1)
|
|
{
|
|
w=cntbuf[nc+i];
|
|
cvrms+=w*CMath::Sqr((w-1.0)/(sr-1.0)-1.0);
|
|
cvrms+=(sr-w)*CMath::Sqr(w/(sr-1.0));
|
|
}
|
|
else
|
|
{
|
|
w=cntbuf[nc+i];
|
|
cvrms+=w*CMath::Sqr(1.0/(double)nc-1.0);
|
|
cvrms+=(sr-w)*CMath::Sqr(1.0/(double)nc);
|
|
}
|
|
}
|
|
//--- change value
|
|
cvrms=MathSqrt(cvrms/(double)(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. |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSSplitK(double &ca[],int &cc[],const int n,const int nc,
|
|
int kmax,int &info,double &thresholds[],int &ni,
|
|
double &cve)
|
|
{
|
|
CRowDouble A=ca;
|
|
CRowInt C=cc;
|
|
CRowDouble Thresholds=thresholds;
|
|
DSSplitK(A,C,n,nc,kmax,info,Thresholds,ni,cve);
|
|
Thresholds.ToArray(thresholds);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSSplitK(CRowDouble &ca,CRowInt &cc,const int n,const int nc,
|
|
int kmax,int &info,CRowDouble &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
|
|
CRowInt ties;
|
|
CRowInt p1;
|
|
CRowInt p2;
|
|
CRowInt cnt;
|
|
CRowInt bestsizes;
|
|
CRowInt cursizes;
|
|
CRowDouble a=ca;
|
|
CRowInt c=cc;
|
|
a.Resize(n);
|
|
c.Resize(n);
|
|
//--- 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; 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; i++)
|
|
{
|
|
//--- check
|
|
if(p2[i]!=i)
|
|
c.Swap(i,p2[i]);
|
|
}
|
|
//--- Special cases
|
|
if(tiecount==1)
|
|
{
|
|
info=-3;
|
|
return;
|
|
}
|
|
//--- General case:
|
|
//--- 0. allocate arrays
|
|
kmax=MathMin(kmax,tiecount);
|
|
//--- allocation
|
|
bestsizes.Resize(kmax);
|
|
cursizes.Resize(kmax);
|
|
cnt.Resize(nc);
|
|
//--- General case:
|
|
//--- 1. prepare "weak" solution (two subintervals,divided at median)
|
|
v2=CMath::m_maxrealnumber;
|
|
j=-1;
|
|
for(i=1; i<tiecount; 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.Set(0,ties[j]);
|
|
bestsizes.Set(1,n-j);
|
|
bestcve=0;
|
|
//--- calculation
|
|
cnt.Fill(0);
|
|
for(i=0; i<j; i++)
|
|
TieAddC(c,ties,i,nc,cnt);
|
|
bestcve+=GetCV(cnt,nc);
|
|
//--- calculation
|
|
cnt.Fill(0);
|
|
for(i=j; i<=tiecount-1; i++)
|
|
TieAddC(c,ties,i,nc,cnt);
|
|
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
|
|
cursizes.Fill(0,0,k);
|
|
//--- change values
|
|
i=0;
|
|
j=0;
|
|
//--- cycle
|
|
while(j<tiecount && i<k)
|
|
{
|
|
//--- Rule: I-th bin is empty,fill it
|
|
if(cursizes[i]==0)
|
|
{
|
|
cursizes.Set(i,ties[j+1]-ties[j]);
|
|
j++;
|
|
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++;
|
|
continue;
|
|
}
|
|
//--- Rule: last bin,always place in current
|
|
if(i==(k-1))
|
|
{
|
|
cursizes.Add(i,ties[j+1]-ties[j]);
|
|
j++;
|
|
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.Add(i,ties[j+1]-ties[j]);
|
|
j++;
|
|
}
|
|
else
|
|
i++;
|
|
}
|
|
//--- 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; i++)
|
|
{
|
|
//--- calculation
|
|
cnt.Fill(0,0,nc);
|
|
for(j1=j; j1<j+cursizes[i]; j1++)
|
|
cnt.Add(c[j1],1);
|
|
curcve+=GetCV(cnt,nc);
|
|
j+=cursizes[i];
|
|
}
|
|
//--- Choose best variant
|
|
if(curcve<bestcve)
|
|
{
|
|
bestsizes.Copy(cursizes,0,0,k);
|
|
bestcve=curcve;
|
|
bestk=k;
|
|
}
|
|
}
|
|
//--- Transform from sizes to thresholds
|
|
cve=bestcve;
|
|
ni=bestk;
|
|
//--- allocation
|
|
thresholds.Resize(ni-1);
|
|
j=bestsizes[0];
|
|
//--- calculation
|
|
for(i=1; i<bestk; i++)
|
|
{
|
|
thresholds.Set(i-1,0.5*(a[j-1]+a[j]));
|
|
j+=bestsizes[i];
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Automatic optimal discretization, internal subroutine. |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSOptimalSplitK(double &ca[],int &cc[],const int n,
|
|
const int nc,int kmax,int &info,
|
|
double &thresholds[],int &ni,double &cve)
|
|
{
|
|
CRowDouble A=ca;
|
|
CRowInt C=cc;
|
|
CRowDouble Thresholds=thresholds;
|
|
DSOptimalSplitK(A,C,n,nc,kmax,info,Thresholds,ni,cve);
|
|
Thresholds.ToArray(thresholds);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::DSOptimalSplitK(CRowDouble &ca,CRowInt &cc,const int n,
|
|
const int nc,int kmax,int &info,
|
|
CRowDouble &thresholds,int &ni,double &cve)
|
|
{
|
|
//--- Test for errors in inputs
|
|
if((n<=0 || nc<2) || kmax<2)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
//--- 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
|
|
CRowInt ties;
|
|
CRowInt p1;
|
|
CRowInt p2;
|
|
CRowInt cnt;
|
|
CRowInt cnt2;
|
|
//--- copy
|
|
CRowDouble a=ca;
|
|
CRowInt c=cc;
|
|
a.Resize(n);
|
|
c.Resize(n);
|
|
//--- create matrix
|
|
CMatrixDouble cv;
|
|
CMatrixInt splits;
|
|
//--- initialization
|
|
info=0;
|
|
ni=0;
|
|
cve=0;
|
|
for(i=0; i<n; 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; i++)
|
|
//--- check
|
|
if(p2[i]!=i)
|
|
c.Swap(i,p2[i]);
|
|
//--- 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);
|
|
cnt.Resize(nc);
|
|
cnt2.Resize(nc);
|
|
//--- calculation
|
|
cnt.Fill(0);
|
|
for(j=0; j<tiecount; j++)
|
|
{
|
|
TieAddC(c,ties,j,nc,cnt);
|
|
splits.Set(0,j,0);
|
|
cv.Set(0,j,GetCV(cnt,nc));
|
|
}
|
|
for(k=1; k<=kmax-1; k++)
|
|
{
|
|
cnt.Fill(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]
|
|
cnt2=cnt;
|
|
cv.Set(k,j,cv.Get(k-1,j-1)+GetCV(cnt2,nc));
|
|
splits.Set(k,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.Get(k-1,s-1)+GetCV(cnt2,nc);
|
|
//--- check
|
|
if(cvtemp<cv.Get(k,j))
|
|
{
|
|
cv.Set(k,j,cvtemp);
|
|
splits.Set(k,j,s);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--- Choose best partition,output result
|
|
koptimal=-1;
|
|
cvoptimal=CMath::m_maxrealnumber;
|
|
for(k=0; k<kmax; k++)
|
|
{
|
|
//--- check
|
|
if(cv.Get(k,tiecount-1)<cvoptimal)
|
|
{
|
|
cvoptimal=cv.Get(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
|
|
thresholds.Resize(1);
|
|
//--- change values
|
|
thresholds.Set(0,0.5*(a[ties[j-1]]+a[ties[j]]));
|
|
ni=2;
|
|
cve=0;
|
|
//--- calculation
|
|
cnt.Fill(0);
|
|
for(i=0; i<j; i++)
|
|
TieAddC(c,ties,i,nc,cnt);
|
|
cve+=GetCV(cnt,nc);
|
|
cnt.Fill(0);
|
|
for(i=j; i<=tiecount-1; i++)
|
|
TieAddC(c,ties,i,nc,cnt);
|
|
cve+=GetCV(cnt,nc);
|
|
}
|
|
else
|
|
{
|
|
//--- General case: 2 or more intervals
|
|
thresholds.Resize(koptimal);
|
|
ni=koptimal+1;
|
|
cve=cv.Get(koptimal,tiecount-1);
|
|
jl=splits.Get(koptimal,tiecount-1);
|
|
jr=tiecount-1;
|
|
//--- calculation
|
|
for(k=koptimal; k>=1; k--)
|
|
{
|
|
thresholds.Set(k-1,0.5*(a[ties[jl-1]]+a[ties[jl]]));
|
|
jr=jl-1;
|
|
jl=splits.Get(k-1,jl-1);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal function |
|
|
//+------------------------------------------------------------------+
|
|
double CBdSS::XLnY(const double x,const double y)
|
|
{
|
|
//--- check
|
|
if(x==0.0)
|
|
return(0.0);
|
|
//--- return result
|
|
return(x*MathLog(y));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal function, |
|
|
//| returns number of samples of class I in Cnt[I] |
|
|
//+------------------------------------------------------------------+
|
|
double CBdSS::GetCV(CRowInt &cnt,const int nc)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int i=0;
|
|
double s=0;
|
|
//--- calculation
|
|
s=0;
|
|
for(i=0; i<nc; i++)
|
|
s+=cnt[i];
|
|
//--- get result
|
|
result=0;
|
|
for(i=0; i<nc; i++)
|
|
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] |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::TieAddC(CRowInt &c,CRowInt &ties,const int ntie,const int nc,
|
|
CRowInt &cnt)
|
|
{
|
|
for(int i=ties[ntie]; i<ties[ntie+1]; i++)
|
|
cnt.Add(c[i],1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal function,subtracts number of samples of class I in tie |
|
|
//| NTie to Cnt[I] |
|
|
//+------------------------------------------------------------------+
|
|
void CBdSS::TieSubC(CRowInt &c,CRowInt &ties,const int ntie,const int nc,
|
|
CRowInt &cnt)
|
|
{
|
|
for(int i=ties[ntie]; i<ties[ntie+1]; i++)
|
|
cnt.Add(c[i],-1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
struct CDFWorkBuf
|
|
{
|
|
CRowInt m_classpriors;
|
|
CRowInt m_varpool;
|
|
int m_varpoolsize;
|
|
CRowInt m_trnset;
|
|
int m_trnsize;
|
|
CRowDouble m_trnlabelsr;
|
|
CRowInt m_trnlabelsi;
|
|
CRowInt m_oobset;
|
|
int m_oobsize;
|
|
CRowDouble m_ooblabelsr;
|
|
CRowInt m_ooblabelsi;
|
|
CRowDouble m_treebuf;
|
|
CRowDouble m_curvals;
|
|
CRowDouble m_bestvals;
|
|
CRowInt m_tmp0i;
|
|
CRowInt m_tmp1i;
|
|
CRowDouble m_tmp0r;
|
|
CRowDouble m_tmp1r;
|
|
CRowDouble m_tmp2r;
|
|
CRowDouble m_tmp3r;
|
|
CRowInt m_tmpnrms2;
|
|
CRowInt m_classtotals0;
|
|
CRowInt m_classtotals1;
|
|
CRowInt m_classtotals01;
|
|
//--- constructor / destructor
|
|
CDFWorkBuf(void) { m_trnsize=0; m_oobsize=0; }
|
|
~CDFWorkBuf(void) {}
|
|
void Copy(const CDFWorkBuf &obj);
|
|
//--- overloading
|
|
void operator=(const CDFWorkBuf &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CDFWorkBuf::Copy(const CDFWorkBuf &obj)
|
|
{
|
|
m_classpriors=obj.m_classpriors;
|
|
m_varpool=obj.m_varpool;
|
|
m_varpoolsize=obj.m_varpoolsize;
|
|
m_trnset=obj.m_trnset;
|
|
m_trnsize=obj.m_trnsize;
|
|
m_trnlabelsr=obj.m_trnlabelsr;
|
|
m_trnlabelsi=obj.m_trnlabelsi;
|
|
m_oobset=obj.m_oobset;
|
|
m_oobsize=obj.m_oobsize;
|
|
m_ooblabelsr=obj.m_ooblabelsr;
|
|
m_ooblabelsi=obj.m_ooblabelsi;
|
|
m_treebuf=obj.m_treebuf;
|
|
m_curvals=obj.m_curvals;
|
|
m_bestvals=obj.m_bestvals;
|
|
m_tmp0i=obj.m_tmp0i;
|
|
m_tmp1i=obj.m_tmp1i;
|
|
m_tmp0r=obj.m_tmp0r;
|
|
m_tmp1r=obj.m_tmp1r;
|
|
m_tmp2r=obj.m_tmp2r;
|
|
m_tmp3r=obj.m_tmp3r;
|
|
m_tmpnrms2=obj.m_tmpnrms2;
|
|
m_classtotals0=obj.m_classtotals0;
|
|
m_classtotals1=obj.m_classtotals1;
|
|
m_classtotals01=obj.m_classtotals01;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
struct CDFVoteBuf
|
|
{
|
|
CRowDouble m_trntotals;
|
|
CRowDouble m_oobtotals;
|
|
CRowInt m_trncounts;
|
|
CRowInt m_oobcounts;
|
|
CRowDouble m_giniimportances;
|
|
//--- constructor / destructor
|
|
CDFVoteBuf(void) {}
|
|
~CDFVoteBuf(void) {}
|
|
//---
|
|
void Copy(const CDFVoteBuf &obj);
|
|
//--- overloading
|
|
void operator=(const CDFVoteBuf &obj) { Copy(obj); }
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CDFVoteBuf::Copy(const CDFVoteBuf &obj)
|
|
{
|
|
m_trntotals=obj.m_trntotals;
|
|
m_oobtotals=obj.m_oobtotals;
|
|
m_trncounts=obj.m_trncounts;
|
|
m_oobcounts=obj.m_oobcounts;
|
|
m_giniimportances=obj.m_giniimportances;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Permutation importance buffer object, stores permutation - |
|
|
//| related losses for some subset of the dataset + some temporaries |
|
|
//| Losses - array[NVars + 2], stores sum of squared |
|
|
//| residuals for each permutation type: |
|
|
//| * Losses[0..NVars - 1] stores losses for |
|
|
//| permutation in J-th variable |
|
|
//| * Losses[NVars] stores loss for all variables |
|
|
//| being randomly perturbed |
|
|
//| * Losses[NVars + 1] stores loss for unperturbed |
|
|
//| dataset |
|
|
//+------------------------------------------------------------------+
|
|
struct CDFPermimpBuf
|
|
{
|
|
CRowDouble m_losses;
|
|
CRowDouble m_xraw;
|
|
CRowDouble m_xdist;
|
|
CRowDouble m_xcur;
|
|
CRowDouble m_y;
|
|
CRowDouble m_yv;
|
|
CRowDouble m_targety;
|
|
CRowInt m_startnodes;
|
|
//--- constructor / destructor
|
|
CDFPermimpBuf(void) {}
|
|
~CDFPermimpBuf(void) {}
|
|
//---
|
|
void Copy(const CDFPermimpBuf &obj);
|
|
//--- overloading
|
|
void operator=(const CDFPermimpBuf &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CDFPermimpBuf::Copy(const CDFPermimpBuf &obj)
|
|
{
|
|
m_losses=obj.m_losses;
|
|
m_xraw=obj.m_xraw;
|
|
m_xdist=obj.m_xdist;
|
|
m_xcur=obj.m_xcur;
|
|
m_y=obj.m_y;
|
|
m_yv=obj.m_yv;
|
|
m_targety=obj.m_targety;
|
|
m_startnodes=obj.m_startnodes;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
struct CDFTreeBuf
|
|
{
|
|
CRowDouble m_treebuf;
|
|
int m_treeidx;
|
|
//--- constructor / destructor
|
|
CDFTreeBuf(void) { m_treeidx=-1; }
|
|
~CDFTreeBuf(void) {}
|
|
//---
|
|
void Copy(const CDFTreeBuf &obj);
|
|
//--- overloading
|
|
void operator=(const CDFTreeBuf &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CDFTreeBuf::Copy(const CDFTreeBuf &obj)
|
|
{
|
|
m_treebuf=obj.m_treebuf;
|
|
m_treeidx=obj.m_treeidx;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| A random forest (decision forest) builder object. |
|
|
//| Used to store dataset and specify decision forest training |
|
|
//| algorithm settings. |
|
|
//+------------------------------------------------------------------+
|
|
struct CDecisionForestBuilder
|
|
{
|
|
int m_DSType;
|
|
int m_NPoints;
|
|
int m_NVars;
|
|
int m_NClasses;
|
|
CRowDouble m_DSData;
|
|
CRowDouble m_DSRVal;
|
|
CRowInt m_DSIVal;
|
|
int m_RDFAlgo;
|
|
double m_RDFRatio;
|
|
double m_RDFVars;
|
|
int m_RDFGlobalSeed;
|
|
int m_RDFSplitStrength;
|
|
int m_RDFImportance;
|
|
CRowDouble m_DSMin;
|
|
CRowDouble m_DSMax;
|
|
bool m_DSBinary[];
|
|
double m_DSRAvg;
|
|
CRowInt m_DSCTotals;
|
|
int m_RDFProgress;
|
|
int m_RDFTotal;
|
|
bool m_NeedIOBMatrix;
|
|
CMatrixInt m_IOBMatrix;
|
|
CRowInt m_VarImpShuffle2;
|
|
CDFWorkBuf m_WorkBuf[];
|
|
CDFVoteBuf m_VoteBuf[];
|
|
CDFTreeBuf m_TreeBuf[];
|
|
//--- constructor / destructor
|
|
CDecisionForestBuilder(void);
|
|
~CDecisionForestBuilder(void) {}
|
|
//---
|
|
void Copy(const CDecisionForestBuilder &obj);
|
|
//--- overloading
|
|
void operator=(const CDecisionForestBuilder &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CDecisionForestBuilder::CDecisionForestBuilder(void)
|
|
{
|
|
m_DSType=0;
|
|
m_NPoints=0;
|
|
m_NVars=0;
|
|
m_NClasses=0;
|
|
m_RDFAlgo=0;
|
|
m_RDFRatio=0;
|
|
m_RDFVars=0;
|
|
m_RDFGlobalSeed=0;
|
|
m_RDFSplitStrength=0;
|
|
m_RDFImportance=0;
|
|
m_DSRAvg=0;
|
|
m_RDFProgress=0;
|
|
m_RDFTotal=0;
|
|
m_NeedIOBMatrix=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CDecisionForestBuilder::Copy(const CDecisionForestBuilder &obj)
|
|
{
|
|
m_DSType=obj.m_DSType;;
|
|
m_NPoints=obj.m_NPoints;
|
|
m_NVars=obj.m_NVars;
|
|
m_NClasses=obj.m_NClasses;
|
|
m_DSData=obj.m_DSData;
|
|
m_DSRVal=obj.m_DSRVal;
|
|
m_DSIVal=obj.m_DSIVal;
|
|
m_RDFAlgo=obj.m_RDFAlgo;
|
|
m_RDFRatio=obj.m_RDFRatio;
|
|
m_RDFVars=obj.m_RDFVars;
|
|
m_RDFGlobalSeed=obj.m_RDFGlobalSeed;
|
|
m_RDFSplitStrength=obj.m_RDFSplitStrength;
|
|
m_RDFImportance=obj.m_RDFImportance;
|
|
m_DSMin=obj.m_DSMin;
|
|
m_DSMax=obj.m_DSMax;
|
|
ArrayCopy(m_DSBinary,obj.m_DSBinary);
|
|
m_DSRAvg=obj.m_DSRAvg;
|
|
m_DSCTotals=obj.m_DSCTotals;
|
|
m_RDFProgress=obj.m_RDFProgress;
|
|
m_RDFTotal=obj.m_RDFTotal;
|
|
m_NeedIOBMatrix=obj.m_NeedIOBMatrix;
|
|
m_IOBMatrix=obj.m_IOBMatrix;
|
|
m_VarImpShuffle2=obj.m_VarImpShuffle2;
|
|
int total=ArraySize(obj.m_WorkBuf);
|
|
ArrayResize(m_WorkBuf,total);
|
|
ArrayResize(m_VoteBuf,total);
|
|
ArrayResize(m_TreeBuf,total);
|
|
for(int i=0; i<total; i++)
|
|
{
|
|
m_WorkBuf[i]=obj.m_WorkBuf[i];
|
|
m_VoteBuf[i]=obj.m_VoteBuf[i];
|
|
m_TreeBuf[i]=obj.m_TreeBuf[i];
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Buffer object which is used to perform various requests(usually |
|
|
//| model inference) in the multithreaded mode(multiple threads |
|
|
//| working with same DF object). |
|
|
//| This object should be created with DFCreateBuffer(). |
|
|
//+------------------------------------------------------------------+
|
|
struct CDecisionForestBuffer
|
|
{
|
|
CRowDouble m_x;
|
|
CRowDouble m_y;
|
|
//--- constructor / destructor
|
|
CDecisionForestBuffer(void) {}
|
|
~CDecisionForestBuffer(void) {}
|
|
//---
|
|
void Copy(const CDecisionForestBuffer &obj);
|
|
//--- overloading
|
|
void operator=(const CDecisionForestBuffer &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CDecisionForestBuffer::Copy(const CDecisionForestBuffer &obj)
|
|
{
|
|
m_x=obj.m_x;
|
|
m_y=obj.m_y;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Decision forest (random forest) model. |
|
|
//+------------------------------------------------------------------+
|
|
class CDecisionForest
|
|
{
|
|
public:
|
|
int m_ForestFormat;
|
|
bool m_UseMantissa8;
|
|
int m_NVars;
|
|
int m_NClasses;
|
|
int m_NTrees;
|
|
int m_BufSize;
|
|
CRowDouble m_Trees;
|
|
CDecisionForestBuffer m_Buffer;
|
|
CRowInt m_Trees8;
|
|
//--- constructor, destructor
|
|
CDecisionForest(void) { m_ForestFormat=0; m_UseMantissa8=false; m_NVars=0; m_NClasses=0; m_NTrees=0; m_BufSize=0; }
|
|
~CDecisionForest(void) {}
|
|
//--- copy
|
|
void Copy(const CDecisionForest &obj);
|
|
//--- overloading
|
|
void operator=(const CDecisionForest &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CDecisionForest::Copy(const CDecisionForest &obj)
|
|
{
|
|
//--- copy variables
|
|
m_ForestFormat=obj.m_ForestFormat;
|
|
m_UseMantissa8=obj.m_UseMantissa8;
|
|
m_NVars=obj.m_NVars;
|
|
m_NClasses=obj.m_NClasses;
|
|
m_NTrees=obj.m_NTrees;
|
|
m_BufSize=obj.m_BufSize;
|
|
m_Trees=obj.m_Trees;
|
|
m_Buffer=obj.m_Buffer;
|
|
m_Trees8=obj.m_Trees8;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This class is a shell for class CDecisionForest |
|
|
//+------------------------------------------------------------------+
|
|
class CDecisionForestShell
|
|
{
|
|
private:
|
|
CDecisionForest m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CDecisionForestShell(void) {}
|
|
CDecisionForestShell(CDecisionForest &obj) { m_innerobj.Copy(obj); }
|
|
~CDecisionForestShell(void) {}
|
|
//--- method
|
|
CDecisionForest *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Decision forest training report. |
|
|
//| === training/oob errors ======================================== |
|
|
//| Following fields store training set errors: |
|
|
//| * relclserror - fraction of misclassified cases, [0,1] |
|
|
//| * avgce - average cross-entropy in bits per symbol |
|
|
//| * rmserror - root-mean-square error |
|
|
//| * avgerror - average error |
|
|
//| * avgrelerror - average relative error |
|
|
//| Out-of-bag estimates are stored in fields with same names, but |
|
|
//| "oob" prefix. |
|
|
//| For classification problems: |
|
|
//| * RMS, AVG and AVGREL errors are calculated for posterior |
|
|
//| probabilities |
|
|
//| For regression problems: |
|
|
//| * RELCLS and AVGCE errors are zero |
|
|
//| === variable importance ======================================== |
|
|
//| Following fields are used to store variable importance |
|
|
//| information: |
|
|
//| * topvars - variables ordered from the most important |
|
|
//| to less important ones (according to |
|
|
//| current choice of importance raiting). |
|
|
//| For example, topvars[0] contains index of |
|
|
//| the most important variable, and |
|
|
//| topvars[0:2] are indexes of 3 most |
|
|
//| important ones and so on. |
|
|
//| * varimportances - array[nvars], ratings (the larger, the |
|
|
//| more important the variable is, always in |
|
|
//| [0,1] range). By default, filled by zeros |
|
|
//| (no importance ratings are provided unless|
|
|
//| you explicitly request them). Zero rating |
|
|
//| means that variable is not important, |
|
|
//| however you will rarely encounter such |
|
|
//| a thing, in many cases unimportant |
|
|
//| variables produce nearly-zero (but |
|
|
//| nonzero) ratings. Variable importance |
|
|
//| report must be EXPLICITLY requested by |
|
|
//| calling: |
|
|
//| * DFBuilderSetImportanceGini() function, |
|
|
//| if you need out-of-bag Gini-based |
|
|
//| importance rating also known as MDI |
|
|
//| (fast to calculate, resistant to |
|
|
//| overfitting issues, but has some bias |
|
|
//| towards continuous and high-cardinality |
|
|
//| categorical variables) |
|
|
//| * DFBuilderSetImportanceTrnGini() function|
|
|
//| if you need training set Gini-based |
|
|
//| importance rating (what other packages |
|
|
//| typically report). |
|
|
//| * DFBuilderSetImportancePermutation() |
|
|
//| function, if you need permutation-based |
|
|
//| importance rating also known as MDA |
|
|
//| (slower to calculate, but less biased) |
|
|
//| * DFBuilderSetImportancenOne() function, |
|
|
//| if you do not need importance ratings - |
|
|
//| ratings will be zero, topvars[] will be |
|
|
//| [0,1,2,...] |
|
|
//| Different importance ratings (Gini or permutation) produce |
|
|
//| non-comparable values. Although in all cases rating values lie in|
|
|
//| [0,1] range, there are exist differences: |
|
|
//| * informally speaking, Gini importance rating tends to divide |
|
|
//| "unit amount of importance" between several important |
|
|
//| variables, i.e. it produces estimates which roughly sum |
|
|
//| to 1.0 (or less than 1.0, if your task can not be solved |
|
|
//| exactly). If all variables are equally important, they will |
|
|
//| have same rating, roughly 1/NVars, even if every variable is |
|
|
//| critically important. |
|
|
//| * from the other side, permutation importance tells us what |
|
|
//| percentage of the model predictive power will be ruined by |
|
|
//| permuting this specific variable. It does not produce |
|
|
//| estimates which sum to one. Critically important variable |
|
|
//| will have rating close to 1.0, and you may have multiple |
|
|
//| variables with such a rating. |
|
|
//| More information on variable importance ratings can be found in |
|
|
//| comments on the DFBuilderSetImportanceGini() and |
|
|
//| DFBuilderSetImportancePermutation() functions. |
|
|
//+------------------------------------------------------------------+
|
|
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;
|
|
CRowInt m_topvars;
|
|
CRowDouble m_varimportances;
|
|
//--- constructor, destructor
|
|
CDFReport(void);
|
|
~CDFReport(void) {}
|
|
//--- copy
|
|
void Copy(const CDFReport &obj);
|
|
//--- overloading
|
|
void operator=(const CDFReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CDFReport::CDFReport(void)
|
|
{
|
|
m_RelCLSError=0;
|
|
m_AvgCE=0;
|
|
m_RMSError=0;
|
|
m_AvgError=0;
|
|
m_AvgRelError=0;
|
|
m_oobrelclserror=0;
|
|
m_oobavgce=0;
|
|
m_oobrmserror=0;
|
|
m_oobavgerror=0;
|
|
m_oobavgrelerror=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CDFReport::Copy(const 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;
|
|
m_topvars=obj.m_topvars;
|
|
m_varimportances=obj.m_varimportances;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This class is a shell for class CDFReport |
|
|
//+------------------------------------------------------------------+
|
|
class CDFReportShell
|
|
{
|
|
private:
|
|
CDFReport m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CDFReportShell(void) {}
|
|
CDFReportShell(CDFReport &obj) { m_innerobj.Copy(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);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable relclserror |
|
|
//+------------------------------------------------------------------+
|
|
double CDFReportShell::GetRelClsError(void)
|
|
{
|
|
return(m_innerobj.m_RelCLSError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable relclserror |
|
|
//+------------------------------------------------------------------+
|
|
void CDFReportShell::SetRelClsError(const double d)
|
|
{
|
|
m_innerobj.m_RelCLSError=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgce |
|
|
//+------------------------------------------------------------------+
|
|
double CDFReportShell::GetAvgCE(void)
|
|
{
|
|
return(m_innerobj.m_AvgCE);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgce |
|
|
//+------------------------------------------------------------------+
|
|
void CDFReportShell::SetAvgCE(const double d)
|
|
{
|
|
m_innerobj.m_AvgCE=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
double CDFReportShell::GetRMSError(void)
|
|
{
|
|
return(m_innerobj.m_RMSError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
void CDFReportShell::SetRMSError(const double d)
|
|
{
|
|
m_innerobj.m_RMSError=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
double CDFReportShell::GetAvgError(void)
|
|
{
|
|
return(m_innerobj.m_AvgError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
void CDFReportShell::SetAvgError(const double d)
|
|
{
|
|
m_innerobj.m_AvgError=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
double CDFReportShell::GetAvgRelError(void)
|
|
{
|
|
return(m_innerobj.m_AvgRelError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
void CDFReportShell::SetAvgRelError(const double d)
|
|
{
|
|
m_innerobj.m_AvgRelError=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable oobrelclserror |
|
|
//+------------------------------------------------------------------+
|
|
double CDFReportShell::GetOOBRelClsError(void)
|
|
{
|
|
return(m_innerobj.m_oobrelclserror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable oobrelclserror |
|
|
//+------------------------------------------------------------------+
|
|
void CDFReportShell::SetOOBRelClsError(const double d)
|
|
{
|
|
m_innerobj.m_oobrelclserror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable oobavgce |
|
|
//+------------------------------------------------------------------+
|
|
double CDFReportShell::GetOOBAvgCE(void)
|
|
{
|
|
return(m_innerobj.m_oobavgce);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable oobavgce |
|
|
//+------------------------------------------------------------------+
|
|
void CDFReportShell::SetOOBAvgCE(const double d)
|
|
{
|
|
m_innerobj.m_oobavgce=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable oobrmserror |
|
|
//+------------------------------------------------------------------+
|
|
double CDFReportShell::GetOOBRMSError(void)
|
|
{
|
|
return(m_innerobj.m_oobrmserror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable oobrmserror |
|
|
//+------------------------------------------------------------------+
|
|
void CDFReportShell::SetOOBRMSError(const double d)
|
|
{
|
|
m_innerobj.m_oobrmserror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable oobavgerror |
|
|
//+------------------------------------------------------------------+
|
|
double CDFReportShell::GetOOBAvgError(void)
|
|
{
|
|
return(m_innerobj.m_oobavgerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable oobavgerror |
|
|
//+------------------------------------------------------------------+
|
|
void CDFReportShell::SetOOBAvgError(const double d)
|
|
{
|
|
m_innerobj.m_oobavgerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable oobavgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
double CDFReportShell::GetOOBAvgRelError(void)
|
|
{
|
|
return(m_innerobj.m_oobavgrelerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable oobavgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
void CDFReportShell::SetOOBAvgRelError(const double d)
|
|
{
|
|
m_innerobj.m_oobavgrelerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CDFReport *CDFReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary class for CDForest |
|
|
//+------------------------------------------------------------------+
|
|
struct CDFInternalBuffers
|
|
{
|
|
//--- arrays
|
|
CRowDouble m_treebuf;
|
|
CRowInt m_idxbuf;
|
|
CRowDouble m_tmpbufr;
|
|
CRowDouble m_tmpbufr2;
|
|
CRowInt m_tmpbufi;
|
|
CRowInt m_classibuf;
|
|
CRowDouble m_sortrbuf;
|
|
CRowDouble m_sortrbuf2;
|
|
CRowInt m_sortibuf;
|
|
CRowInt m_varpool;
|
|
bool m_evsbin[];
|
|
CRowDouble m_evssplits;
|
|
//--- constructor, destructor
|
|
CDFInternalBuffers(void) {}
|
|
~CDFInternalBuffers(void) {}
|
|
//---
|
|
void Copy(const CDFInternalBuffers &obj);
|
|
//--- overloading
|
|
void operator=(const CDFInternalBuffers &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CDFInternalBuffers::Copy(const CDFInternalBuffers &obj)
|
|
{
|
|
//---
|
|
m_treebuf=obj.m_treebuf;
|
|
m_idxbuf=obj.m_idxbuf;
|
|
m_tmpbufr=obj.m_tmpbufr;
|
|
m_tmpbufr2=obj.m_tmpbufr2;
|
|
m_tmpbufi=obj.m_tmpbufi;
|
|
m_classibuf=obj.m_classibuf;
|
|
m_sortrbuf=obj.m_sortrbuf;
|
|
m_sortrbuf2=obj.m_sortrbuf2;
|
|
m_sortibuf=obj.m_sortibuf;
|
|
m_varpool=obj.m_varpool;
|
|
m_evssplits=obj.m_evssplits;
|
|
//---
|
|
ArrayCopy(m_evsbin,obj.m_evsbin);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Decision forest class |
|
|
//+------------------------------------------------------------------+
|
|
class CDForest
|
|
{
|
|
public:
|
|
//--- class constants
|
|
static const int m_InnerNodeWidth;
|
|
static const int m_LeafNodeWdth;
|
|
static const int m_DFUseStrongSplits;
|
|
static const int m_DFUseEVS;
|
|
static const int m_DFUncompressedV0;
|
|
static const int m_DFCompressedV0;
|
|
static const int m_NeedTrnGini;
|
|
static const int m_NeedOOBGini;
|
|
static const int m_NeedPermutation;
|
|
static const int m_PermutationImportanceBatchSize;
|
|
|
|
//--- public methods
|
|
static void DFCreateBuffer(CDecisionForest &model,CDecisionForestBuffer &buf);
|
|
static void DFBuilderCreate(CDecisionForestBuilder &s);
|
|
static void DFBuilderSetDataset(CDecisionForestBuilder &s,CMatrixDouble &xy,int npoints,int nvars,int nclasses);
|
|
static void DFBuilderSetRndVars(CDecisionForestBuilder &s,int rndvars);
|
|
static void DFBuilderSetRndVarsRatio(CDecisionForestBuilder &s,double f);
|
|
static void DFBuilderSetRndVarsAuto(CDecisionForestBuilder &s);
|
|
static void DFBuilderSetSubsampleRatio(CDecisionForestBuilder &s,double f);
|
|
static void DFBuilderSetSeed(CDecisionForestBuilder &s,int seedval);
|
|
static void DFBuilderSetRDFAlgo(CDecisionForestBuilder &s,int algotype);
|
|
static void DFBuilderSetRDFSplitStrength(CDecisionForestBuilder &s,int splitstrength);
|
|
static void DFBuilderSetImportanceTrnGini(CDecisionForestBuilder &s);
|
|
static void DFBuilderSetImportanceOOBGini(CDecisionForestBuilder &s);
|
|
static void DFBuilderSetImportancePermutation(CDecisionForestBuilder &s);
|
|
static void DFBuilderSetImportanceNone(CDecisionForestBuilder &s);
|
|
static double DFBuilderGetProgress(CDecisionForestBuilder &s);
|
|
static double DFBuilderPeekProgress(CDecisionForestBuilder &s);
|
|
static void DFBuilderBuildRandomForest(CDecisionForestBuilder &s,int ntrees,CDecisionForest &df,CDFReport &rep);
|
|
static double DFBinaryCompression(CDecisionForest &df);
|
|
static double DFBinaryCompression8(CDecisionForest &df);
|
|
static void DFProcess(CDecisionForest &df,double &x[],double &y[]);
|
|
static void DFProcess(CDecisionForest &df,CRowDouble &x,CRowDouble &y);
|
|
static void DFProcessI(CDecisionForest &df,double &x[],double &y[]);
|
|
static void DFProcessI(CDecisionForest &df,CRowDouble &x,CRowDouble &y);
|
|
static double DFProcess0(CDecisionForest &model,CRowDouble &x);
|
|
static int DFClassify(CDecisionForest &model,CRowDouble &x);
|
|
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);
|
|
//---
|
|
static void DFBuildRandomDecisionForest(CMatrixDouble &xy,int npoints,int nvars,int nclasses,int ntrees,double r,int &info,CDecisionForest &df,CDFReport &rep);
|
|
static void DFBuildRandomDecisionForestX1(CMatrixDouble &xy,int npoints,int nvars,int nclasses,int ntrees,int nrndvars,double r,int &info,CDecisionForest &df,CDFReport &rep);
|
|
static void DFBuildInternal(CMatrixDouble &xy,int npoints,int nvars,int nclasses,int ntrees,int samplesize,int nfeatures,int flags,int &info,CDecisionForest &df,CDFReport &rep);
|
|
|
|
private:
|
|
static void BuildRandomTree(CDecisionForestBuilder &s,int treeidx0,int treeidx1);
|
|
static void BuildRandomTreeRec(CDecisionForestBuilder &s,CDFWorkBuf &workbuf,int workingset,int varstoselect,CRowDouble &treebuf,CDFVoteBuf &votebuf,CHighQualityRandState &rs,int idx0,int idx1,int oobidx0,int oobidx1,double meanloss,double topmostmeanloss,int &treesize);
|
|
static void EstimateVariableImportance(CDecisionForestBuilder &s,int sessionseed,CDecisionForest &df,int ntrees,CDFReport &rep);
|
|
static void EstimatePermutationImportances(CDecisionForestBuilder &s,CDecisionForest &df,int ntrees,CDFPermimpBuf &permimpbuf,int idx0,int idx1);
|
|
static void CleanReport(CDecisionForestBuilder &s,CDFReport &rep);
|
|
static double MeanNRMS2(int nclasses,CRowInt &trnlabelsi,CRowDouble &trnlabelsr,int trnidx0,int trnidx1,CRowInt &tstlabelsi,CRowDouble &tstlabelsr,int tstidx0,int tstidx1,CRowInt &tmpi);
|
|
static void ChooseCurrentSplitDense(CDecisionForestBuilder &s,CDFWorkBuf &workbuf,int &varsinpool,int varstoselect,CHighQualityRandState &rs,int idx0,int idx1,int &varbest,double &splitbest);
|
|
static void EvaluateDenseSplit(CDecisionForestBuilder &s,CDFWorkBuf &workbuf,CHighQualityRandState &rs,int splitvar,int idx0,int idx1,int &info,double &split,double &rms);
|
|
static void ClassifierSplit(CDecisionForestBuilder &s,CDFWorkBuf &workbuf,CRowDouble &x,CRowInt &c,int n,CHighQualityRandState &rs,int &info,double &threshold,double &e,CRowDouble &sortrbuf,CRowInt &sortibuf);
|
|
static void RegressionSplit(CDecisionForestBuilder &s,CDFWorkBuf &workbuf,CRowDouble &x,CRowDouble &y,int n,int &info,double &threshold,double &e,CRowDouble &sortrbuf,CRowDouble &sortrbuf2);
|
|
static double GetSplit(CDecisionForestBuilder &s,double a,double b,CHighQualityRandState &rs);
|
|
static void OutputLeaf(CDecisionForestBuilder &s,CDFWorkBuf &workbuf,CRowDouble &treebuf,CDFVoteBuf &votebuf,int idx0,int idx1,int oobidx0,int oobidx1,int &treesize,double leafval);
|
|
static void AnalyzeAndPreprocessDataset(CDecisionForestBuilder &s);
|
|
static void MergeTrees(CDecisionForestBuilder &s,CDecisionForest &df);
|
|
static void ProcessVotingResults(CDecisionForestBuilder &s,int ntrees,CDFVoteBuf &buf,CDFReport &rep);
|
|
static double BinaryCompression(CDecisionForest &df,bool usemantissa8);
|
|
static int ComputeCompressedSizeRec(CDecisionForest &df,bool usemantissa8,int treeroot,int treepos,CRowInt &compressedsizes,bool savecompressedsizes);
|
|
static void CompressRec(CDecisionForest &df,bool usemantissa8,int treeroot,int treepos,CRowInt &compressedsizes,CRowInt &buf,int &dstoffs);
|
|
static int ComputeCompressedUintSize(int v);
|
|
static void StreamUint(CRowInt &buf,int &offs,int v);
|
|
static int UnstreamUint(CRowInt &buf,int &offs);
|
|
static void StreamFloat(CRowInt &buf,bool usemantissa8,int &offs,double v);
|
|
static double UnstreamFloat(CRowInt &buf,bool usemantissa8,int &off);
|
|
|
|
static int DFClsError(CDecisionForest &df,CMatrixDouble &xy,const int npoints);
|
|
static void DFProcessInternalUncompressed(CDecisionForest &df,int subtreeroot,int nodeoffs,CRowDouble &x,CRowDouble &y);
|
|
static void DFProcessInternalCompressed(CDecisionForest &df,int offs,CRowDouble &x,CRowDouble &y);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize constants |
|
|
//+------------------------------------------------------------------+
|
|
const int CDForest::m_InnerNodeWidth=3;
|
|
const int CDForest::m_LeafNodeWdth=2;
|
|
const int CDForest::m_DFUseStrongSplits=1;
|
|
const int CDForest::m_DFUseEVS=2;
|
|
const int CDForest::m_DFUncompressedV0=0;
|
|
const int CDForest::m_DFCompressedV0=1;
|
|
const int CDForest::m_NeedTrnGini=1;
|
|
const int CDForest::m_NeedOOBGini=2;
|
|
const int CDForest::m_NeedPermutation=3;
|
|
const int CDForest::m_PermutationImportanceBatchSize=512;
|
|
//+------------------------------------------------------------------+
|
|
//| This function creates buffer structure which can be used to |
|
|
//| perform parallel inference requests. |
|
|
//| DF subpackage provides two sets of computing functions - ones |
|
|
//| which use internal buffer of DF model (these functions are |
|
|
//| single-threaded because they use same buffer, which can not |
|
|
//| shared between threads), and ones which use external buffer. |
|
|
//| This function is used to initialize external buffer. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - DF model which is associated with newly created |
|
|
//| buffer |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Buf - external buffer. |
|
|
//| IMPORTANT: buffer object should be used only with model which was|
|
|
//| used to initialize buffer. Any attempt to use buffer |
|
|
//| with different object is dangerous - you may get |
|
|
//| integrity check failure (exception) because sizes of |
|
|
//| internal arrays do not fit to dimensions of the model |
|
|
//| structure. |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFCreateBuffer(CDecisionForest &model,
|
|
CDecisionForestBuffer &buf)
|
|
{
|
|
buf.m_x.Resize(model.m_NVars);
|
|
buf.m_y.Resize(model.m_NClasses);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine creates CDecisionForestBuilder object which is |
|
|
//| used to train decision forests. |
|
|
//| By default, new builder stores empty dataset and some reasonable |
|
|
//| default settings. At the very least, you should specify dataset |
|
|
//| prior to building decision forest. You can also tweak settings of|
|
|
//| the forest construction algorithm (recommended, although default |
|
|
//| setting should work well). |
|
|
//| Following actions are mandatory: |
|
|
//| * calling DFBuilderSetDataset() to specify dataset |
|
|
//| * calling DFBuilderBuildRandomForest() to build decision forest|
|
|
//| using current dataset and default settings |
|
|
//| Additionally, you may call: |
|
|
//| * DFBuilderSetRndVars() or DFBuilderSetRndVarsRatio() to |
|
|
//| specify number of variables randomly chosen for each split |
|
|
//| * DFBuilderSetSubsampleRatio() to specify fraction of the |
|
|
//| dataset randomly subsampled to build each tree |
|
|
//| * DFBuilderSetSeed() to control random seed chosen for tree |
|
|
//| construction |
|
|
//| INPUT PARAMETERS: |
|
|
//| none |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderCreate(CDecisionForestBuilder &s)
|
|
{
|
|
//--- Empty dataset
|
|
s.m_DSType=-1;
|
|
s.m_NPoints=0;
|
|
s.m_NVars=0;
|
|
s.m_NClasses=1;
|
|
//--- Default training settings
|
|
s.m_RDFAlgo=0;
|
|
s.m_RDFRatio=0.5;
|
|
s.m_RDFVars=0.0;
|
|
s.m_RDFGlobalSeed=0;
|
|
s.m_RDFSplitStrength=2;
|
|
s.m_RDFImportance=0;
|
|
//--- Other fields
|
|
s.m_RDFProgress=0;
|
|
s.m_RDFTotal=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine adds dense dataset to the internal storage of the|
|
|
//| builder object. Specifying your dataset in the dense format means|
|
|
//| that the dense version of the forest construction algorithm will |
|
|
//| be invoked. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| XY - array[NPoints,NVars+1] (minimum size; actual size |
|
|
//| can be larger, only leading part is used anyway), |
|
|
//| dataset: |
|
|
//| * first NVars elements of each row store values of |
|
|
//| the independent variables |
|
|
//| * last column store class number(in 0...NClasses-1)|
|
|
//| or real value of the dependent variable |
|
|
//| NPoints - number of rows in the dataset, NPoints>=1 |
|
|
//| NVars - number of independent variables, NVars>=1 |
|
|
//| NClasses - indicates type of the problem being solved: |
|
|
//| * NClasses>=2 means that classification problem is |
|
|
//| solved (last column of the dataset stores class |
|
|
//| number) |
|
|
//| * NClasses=1 means that regression problem is |
|
|
//| solved (last column of the dataset stores |
|
|
//| variable value) |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderSetDataset(CDecisionForestBuilder &s,
|
|
CMatrixDouble &xy,
|
|
int npoints,
|
|
int nvars,
|
|
int nclasses)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
//--- Check parameters
|
|
if(!CAp::Assert(npoints>=1,__FUNCTION__": npoints<1"))
|
|
return;
|
|
if(!CAp::Assert(nvars>=1,__FUNCTION__": nvars<1"))
|
|
return;
|
|
if(!CAp::Assert(nclasses>=1,__FUNCTION__": nclasses<1"))
|
|
return;
|
|
if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": rows(xy)<npoints"))
|
|
return;
|
|
if(!CAp::Assert(xy.Cols()>nvars,__FUNCTION__": cols(xy)<nvars+1"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nvars+1),__FUNCTION__": xy parameter contains INFs or NANs"))
|
|
return;
|
|
//---
|
|
if(nclasses>1)
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
j=(int)MathRound(xy.Get(i,nvars));
|
|
if(!CAp::Assert(j>=0 && j<nclasses,__FUNCTION__": last column of xy contains invalid class number"))
|
|
return;
|
|
}
|
|
//--- Set dataset
|
|
s.m_DSType=0;
|
|
s.m_NPoints=npoints;
|
|
s.m_NVars=nvars;
|
|
s.m_NClasses=nclasses;
|
|
CApServ::RVectorSetLengthAtLeast(s.m_DSData,npoints*nvars);
|
|
for(i=0; i<npoints; i++)
|
|
for(j=0; j<nvars; j++)
|
|
s.m_DSData.Set(j*npoints+i,xy.Get(i,j));
|
|
if(nclasses>1)
|
|
{
|
|
CApServ::IVectorSetLengthAtLeast(s.m_DSIVal,npoints);
|
|
for(i=0; i<npoints; i++)
|
|
s.m_DSIVal.Set(i,(int)MathRound(xy.Get(i,nvars)));
|
|
}
|
|
else
|
|
s.m_DSRVal=xy.Col(nvars)+0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets number of variables (in [1,NVars] range) used |
|
|
//| by decision forest construction algorithm. |
|
|
//| The default option is to use roughly sqrt(NVars) variables. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| RndVars - number of randomly selected variables; values |
|
|
//| outside of [1,NVars] range are silently clipped. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderSetRndVars(CDecisionForestBuilder &s,
|
|
int rndvars)
|
|
{
|
|
s.m_RDFVars=MathMax(rndvars,1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets number of variables used by decision forest |
|
|
//| construction algorithm as a fraction of total variable count |
|
|
//| (0,1) range. |
|
|
//| The default option is to use roughly sqrt(NVars) variables. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| F - round(NVars*F) variables are selected |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderSetRndVarsRatio(CDecisionForestBuilder &s,
|
|
double f)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(f),__FUNCTION__": F is INF or NAN"))
|
|
return;
|
|
s.m_RDFVars=-MathMax(f,CMath::m_machineepsilon);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells decision forest builder to automatically |
|
|
//| choose number of variables used by decision forest construction |
|
|
//| algorithm. Roughly sqrt(NVars) variables will be used. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderSetRndVarsAuto(CDecisionForestBuilder &s)
|
|
{
|
|
s.m_RDFVars=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets size of dataset subsample generated the |
|
|
//| decision forest construction algorithm. Size is specified as a |
|
|
//| fraction of total dataset size. |
|
|
//| The default option is to use 50% of the dataset for training, |
|
|
//| 50% for the OOB estimates. You can decrease fraction F down to |
|
|
//| 10%, 1% or even below in order to reduce overfitting. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| F - fraction of the dataset to use, in (0,1] range. |
|
|
//| Values outside of this range will be silently |
|
|
//| clipped. At least one element is always selected |
|
|
//| for the training set. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderSetSubsampleRatio(CDecisionForestBuilder &s,
|
|
double f)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(f),__FUNCTION__": F is INF or NAN"))
|
|
return;
|
|
s.m_RDFRatio=MathMax(f,CMath::m_machineepsilon);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets seed used by internal RNG for random |
|
|
//| subsampling and random selection of variable subsets. |
|
|
//| By default random seed is used, i.e. every time you build |
|
|
//| decision forest, we seed generator with new value obtained from |
|
|
//| system-wide RNG. Thus, decision forest builder returns |
|
|
//| non-deterministic results. You can change such behavior by |
|
|
//| specyfing fixed positive seed value. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| SeedVal - seed value: |
|
|
//| * positive values are used for seeding RNG with |
|
|
//| fixed seed, i.e. subsequent runs on same data |
|
|
//| will return same decision forests |
|
|
//| * non-positive seed means that random seed is used |
|
|
//| for every run of builder, i.e. subsequent runs |
|
|
//| on same datasets will return slightly different |
|
|
//| decision forests |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder, see |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderSetSeed(CDecisionForestBuilder &s,
|
|
int seedval)
|
|
{
|
|
s.m_RDFGlobalSeed=seedval;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets random decision forest construction algorithm.|
|
|
//| As for now, only one decision forest construction algorithm is |
|
|
//| supported-a dense "baseline" RDF algorithm. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| AlgoType - algorithm type: |
|
|
//| * 0 = baseline dense RDF |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder, see |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderSetRDFAlgo(CDecisionForestBuilder &s,
|
|
int algotype)
|
|
{
|
|
if(!CAp::Assert(algotype==0,__FUNCTION__": unexpected algotype"))
|
|
return;
|
|
s.m_RDFAlgo=algotype;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets split selection algorithm used by decision |
|
|
//| forest classifier. You may choose several algorithms, with |
|
|
//| different speed and quality of the results. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| SplitStrength - split type: |
|
|
//| * 0 = split at the random position, fastest one |
|
|
//| * 1 = split at the middle of the range |
|
|
//| * 2 = strong split at the best point of the range |
|
|
//| (default) |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder, see |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderSetRDFSplitStrength(CDecisionForestBuilder &s,
|
|
int splitstrength)
|
|
{
|
|
if(!CAp::Assert(splitstrength==0 || splitstrength==1 || splitstrength==2,__FUNCTION__": unexpected split type"))
|
|
return;
|
|
s.m_RDFSplitStrength=splitstrength;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells decision forest construction algorithm to use|
|
|
//| Gini impurity based variable importance estimation (also known as|
|
|
//| MDI). |
|
|
//| This version of importance estimation algorithm analyzes mean |
|
|
//| decrease in impurity (MDI) on training sample during splits. The |
|
|
//| result is divided by impurity at the root node in order to |
|
|
//| produce estimate in [0,1] range. |
|
|
//| Such estimates are fast to calculate and beautifully normalized |
|
|
//| (sum to one) but have following downsides: |
|
|
//| * They ALWAYS sum to 1.0, even if output is completely |
|
|
//| unpredictable. I.e. MDI allows to order variables by |
|
|
//| importance, but does not tell us about "absolute" |
|
|
//| importances of variables |
|
|
//| * there exist some bias towards continuous and high-cardinality|
|
|
//| categorical variables |
|
|
//| NOTE: informally speaking, MDA (permutation importance) rating |
|
|
//| answers the question "what part of the model |
|
|
//| predictive power is ruined by permuting k-th variable?" |
|
|
//| while MDI tells us "what part of the model predictive power|
|
|
//| was achieved due to usage of k-th variable". |
|
|
//| Thus, MDA rates each variable independently at "0 to 1" scale |
|
|
//| while MDI (and OOB-MDI too) tends to divide "unit amount of |
|
|
//| importance" between several important variables. |
|
|
//| If all variables are equally important, they will have same |
|
|
//| MDI/OOB-MDI rating, equal (for OOB-MDI: roughly equal) to |
|
|
//| 1/NVars. However, roughly same picture will be produced for |
|
|
//| the "all variables provide information no one is critical" |
|
|
//| situation and for the "all variables are critical, drop any one, |
|
|
//| everything is ruined" situation. |
|
|
//| Contrary to that, MDA will rate critical variable as ~1.0 |
|
|
//| important, and important but non-critical variable will have less|
|
|
//| than unit rating. |
|
|
//| NOTE: quite an often MDA and MDI return same results. It |
|
|
//| generally happens on problems with low test set error |
|
|
//| (a few percents at most) and large enough training set |
|
|
//| to avoid overfitting. |
|
|
//| The difference between MDA, MDI and OOB-MDI becomes important |
|
|
//| only on "hard" tasks with high test set error and/or small |
|
|
//| training set. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder object. Next call to the |
|
|
//| forest construction function will produce: |
|
|
//| * importance estimates in rep.varimportances field |
|
|
//| * variable ranks in rep.topvars field |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderSetImportanceTrnGini(CDecisionForestBuilder &s)
|
|
{
|
|
s.m_RDFImportance=m_NeedTrnGini;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells decision forest construction algorithm to use|
|
|
//| out-of-bag version of Gini variable importance estimation (also |
|
|
//| known as OOB-MDI). |
|
|
//| This version of importance estimation algorithm analyzes mean |
|
|
//| decrease in impurity (MDI) on out-of-bag sample during splits. |
|
|
//| The result is divided by impurity at the root node in order to |
|
|
//| produce estimate in [0,1] range. |
|
|
//| Such estimates are fast to calculate and resistant to overfitting|
|
|
//| issues (thanks to the out-of-bag estimates used). However, OOB |
|
|
//| Gini rating has following downsides: |
|
|
//| * there exist some bias towards continuous and |
|
|
//| high-cardinality categorical variables |
|
|
//| * Gini rating allows us to order variables by importance,|
|
|
//| but it is hard to define importance of the variable by |
|
|
//| itself. |
|
|
//| NOTE: informally speaking, MDA (permutation importance) rating |
|
|
//| answers the question "what part of the model predictive |
|
|
//| power is ruined by permuting k-th variable?" while MDI |
|
|
//| tells us "what part of the model predictive power was |
|
|
//| achieved due to usage of k-th variable". |
|
|
//| Thus, MDA rates each variable independently at "0 to 1" scale |
|
|
//| while MDI (and OOB-MDI too) tends to divide "unit amount of |
|
|
//| importance" between several important variables. |
|
|
//| If all variables are equally important, they will have same |
|
|
//| MDI/OOB-MDI rating, equal (for OOB-MDI: roughly equal) to |
|
|
//| 1/NVars. However, roughly same picture will be produced for the |
|
|
//| "all variables provide information no one is critical" situation |
|
|
//| and for the "all variables are critical, drop any one, everything|
|
|
//| is ruined" situation. |
|
|
//| Contrary to that, MDA will rate critical variable as ~1.0 |
|
|
//| important, and important but non-critical variable will have less|
|
|
//| than unit rating. |
|
|
//| NOTE: quite an often MDA and MDI return same results. It |
|
|
//| generally happens on problems with low test set error |
|
|
//| (a few percents at most) and large enough training set to |
|
|
//| avoid overfitting. |
|
|
//| The difference between MDA, MDI and OOB-MDI becomes important |
|
|
//| only on "hard" tasks with high test set error and/or small |
|
|
//| training set. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder object. Next call to the|
|
|
//| forest construction function will produce: |
|
|
//| * importance estimates in rep.varimportances field |
|
|
//| * variable ranks in rep.topvars field |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderSetImportanceOOBGini(CDecisionForestBuilder &s)
|
|
{
|
|
s.m_RDFImportance=m_NeedOOBGini;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells decision forest construction algorithm to use|
|
|
//| permutation variable importance estimator (also known as MDA). |
|
|
//| This version of importance estimation algorithm analyzes mean |
|
|
//| increase in out-of-bag sum of squared residuals after random |
|
|
//| permutation of J-th variable. The result is divided by error |
|
|
//| computed with all variables being perturbed in order to produce |
|
|
//| R-squared-like estimate in [0,1] range. |
|
|
//| Such estimate is slower to calculate than Gini-based rating |
|
|
//| because it needs multiple inference runs for each of variables |
|
|
//| being studied. |
|
|
//| MDA rating has following benefits over Gini-based ones: |
|
|
//| * no bias towards specific variable types |
|
|
//| *ability to directly evaluate "absolute" importance of some|
|
|
//| variable at "0 to 1" scale (contrary to Gini-based rating,|
|
|
//| which returns comparative importances). |
|
|
//| NOTE: informally speaking, MDA (permutation importance) rating |
|
|
//| answers the question "what part of the model predictive |
|
|
//| power is ruined by permuting k-th variable?" while MDI |
|
|
//| tells us "what part of the model predictive power was |
|
|
//| achieved due to usage of k-th variable". |
|
|
//| Thus, MDA rates each variable independently at "0 to 1" scale |
|
|
//| while MDI (and OOB-MDI too) tends to divide "unit amount of |
|
|
//| importance" between several important variables. |
|
|
//| If all variables are equally important, they will have same |
|
|
//| MDI/OOB-MDI rating, equal (for OOB-MDI: roughly equal) to |
|
|
//| 1/NVars. However, roughly same picture will be produced forthe |
|
|
//| "all variables provide information no one is critical" situation |
|
|
//| and for the "all variables are critical, drop any one, everything|
|
|
//| is ruined" situation. |
|
|
//| Contrary to that, MDA will rate critical variable as ~1.0 |
|
|
//| important, and important but non-critical variable will have less|
|
|
//| than unit rating. |
|
|
//| NOTE: quite an often MDA and MDI return same results. It |
|
|
//| generally happens on problems with low test set error |
|
|
//| (a few percents at most) and large enough training set |
|
|
//| to avoid overfitting. |
|
|
//| The difference between MDA, MDI and OOB-MDI becomes important |
|
|
//| only on "hard" tasks with high test set error and/or small |
|
|
//| training set. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder object. Next call to |
|
|
//| the forest construction function will produce: |
|
|
//| * importance estimates in rep.varimportances field |
|
|
//| * variable ranks in rep.topvars field |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderSetImportancePermutation(CDecisionForestBuilder &s)
|
|
{
|
|
s.m_RDFImportance=m_NeedPermutation;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function tells decision forest construction algorithm to |
|
|
//| skip variable importance estimation. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder object. Next call to the |
|
|
//| forest construction function will result in forest |
|
|
//| being built without variable importance estimation.|
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderSetImportanceNone(CDecisionForestBuilder &s)
|
|
{
|
|
s.m_RDFImportance=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is an alias for dfbuilderpeekprogress(), left in |
|
|
//| ALGLIB for backward compatibility reasons. |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::DFBuilderGetProgress(CDecisionForestBuilder &s)
|
|
{
|
|
return(DFBuilderPeekProgress(s));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is used to peek into decision forest construction |
|
|
//| process from some other thread and get current progress indicator|
|
|
//| It returns value in [0,1]. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object used to build forest|
|
|
//| in some other thread |
|
|
//| RESULT: |
|
|
//| progress value, in [0,1] |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::DFBuilderPeekProgress(CDecisionForestBuilder &s)
|
|
{
|
|
double result=0;
|
|
//--- calculation
|
|
result=s.m_RDFProgress/MathMax(s.m_RDFTotal,1);
|
|
result=MathMax(result,0);
|
|
result=MathMin(result,1);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds decision forest according to current |
|
|
//| settings using dataset internally stored in the builder object. |
|
|
//| Dense algorithm is used. |
|
|
//| NOTE: this function uses dense algorithm for forest construction |
|
|
//| independently from the dataset format (dense or sparse). |
|
|
//| NOTE: forest built with this function is stored in-memory using |
|
|
//| 64-bit data structures for offsets/indexes/split values. It|
|
|
//| is possible to convert forest into more memory-efficient |
|
|
//| compressed binary representation. Depending on the problem |
|
|
//| properties, 3.7x-5.7x compression factors are possible. |
|
|
//| The downsides of compression are (a) slight reduction in the |
|
|
//| model accuracy and (b) ~1.5x reduction in the inference speed |
|
|
//| (due to increased complexity of the storage format). |
|
|
//| See comments on DFBinaryCompression() for more info. |
|
|
//| Default settings are used by the algorithm; you can tweak them |
|
|
//| with the help of the following functions: |
|
|
//| * DFBuilderSetRFactor() - to control a fraction of the |
|
|
//| dataset used for subsampling |
|
|
//| * DFBuilderSetRandomVars() - to control number of variables |
|
|
//| randomly chosen for decision rule |
|
|
//| creation |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| NTrees - NTrees>=1, number of trees to train |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| D - decision forest. You can compress this forest to |
|
|
//| more compact 16-bit representation with |
|
|
//| DFBinaryCompression() |
|
|
//| Rep - report, see below for information on its fields. |
|
|
//| == report information produced by forest construction function = |
|
|
//| Decision forest training report includes following information: |
|
|
//| * training set errors |
|
|
//| * out-of-bag estimates of errors |
|
|
//| * variable importance ratings |
|
|
//| Following fields are used to store information: |
|
|
//| * training set errors are stored in rep.RelCLSError, rep.AvgCE,|
|
|
//| rep.RMSError, rep.AvgError and rep.AvgRelError |
|
|
//| * out-of-bag estimates of errors are stored in |
|
|
//| rep.oobrelclserror, rep.oobavgce, rep.oobrmserror, |
|
|
//| rep.oobavgerror and rep.oobavgrelerror |
|
|
//| Variable importance reports, if requested by |
|
|
//| DFBuilderSetImportanceGini(), DFBuilderSetImportanceTrnGini() or |
|
|
//| DFBuilderSetImportancePermutation() call, are stored in: |
|
|
//| * rep.varimportances field stores importance ratings |
|
|
//| * rep.topvars stores variable indexes ordered from the most |
|
|
//| important to less important ones |
|
|
//| You can find more information about report fields in: |
|
|
//| * comments on CDFReport structure |
|
|
//| * comments on DFBuilderSetImportanceGini function |
|
|
//| * comments on DFBuilderSetImportanceTrnGini function |
|
|
//| * comments on DFBuilderDetImportancePermutation function |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuilderBuildRandomForest(CDecisionForestBuilder &s,
|
|
int ntrees,
|
|
CDecisionForest &df,
|
|
CDFReport &rep)
|
|
{
|
|
//--- create variables
|
|
int nvars=0;
|
|
int nclasses=0;
|
|
int npoints=0;
|
|
int trnsize=0;
|
|
int maxtreesize=0;
|
|
int sessionseed=0;
|
|
CDFVoteBuf buf;
|
|
//--- check
|
|
if(!CAp::Assert(ntrees>=1,__FUNCTION__": ntrees<1"))
|
|
return;
|
|
|
|
CleanReport(s,rep);
|
|
npoints=s.m_NPoints;
|
|
nvars=s.m_NVars;
|
|
nclasses=s.m_NClasses;
|
|
//--- Set up progress counter
|
|
s.m_RDFProgress=0;
|
|
s.m_RDFTotal=ntrees*npoints;
|
|
if(s.m_RDFImportance==m_NeedPermutation)
|
|
s.m_RDFTotal+=ntrees*npoints;
|
|
//--- Quick exit for empty dataset
|
|
if(s.m_DSType==-1 || npoints==0)
|
|
{
|
|
if(!CAp::Assert(m_LeafNodeWdth==2,__FUNCTION__": integrity check failed"))
|
|
return;
|
|
df.m_ForestFormat=m_DFUncompressedV0;
|
|
df.m_NVars=s.m_NVars;
|
|
df.m_NClasses=s.m_NClasses;
|
|
df.m_NTrees=1;
|
|
df.m_BufSize=1+m_LeafNodeWdth;
|
|
df.m_Trees.Resize(1+m_LeafNodeWdth);
|
|
df.m_Trees.Set(0,1+m_LeafNodeWdth);
|
|
df.m_Trees.Set(1,-1.0);
|
|
df.m_Trees.Set(2,0.0);
|
|
DFCreateBuffer(df,df.m_Buffer);
|
|
return;
|
|
}
|
|
if(!CAp::Assert(npoints>0,__FUNCTION__": integrity check failed"))
|
|
return;
|
|
//--- Analyze dataset statistics, perform preprocessing
|
|
AnalyzeAndPreprocessDataset(s);
|
|
//--- Prepare "work", "vote" and "tree" pools and other settings
|
|
trnsize=(int)MathRound(npoints*s.m_RDFRatio);
|
|
trnsize=MathMax(trnsize,1);
|
|
trnsize=MathMin(trnsize,npoints);
|
|
maxtreesize=1+m_InnerNodeWidth*(trnsize-1)+m_LeafNodeWdth*trnsize;
|
|
//--- Allocation
|
|
ResetLastError();
|
|
if(!CAp::Assert(ArrayResize(s.m_WorkBuf,ntrees)==ntrees,StringFormat("%s: resize buffer failed (%d)",__FUNCTION__,GetLastError())))
|
|
return;
|
|
if(!CAp::Assert(ArrayResize(s.m_VoteBuf,ntrees)==ntrees,StringFormat("%s: resize buffer failed (%d)",__FUNCTION__,GetLastError())))
|
|
return;
|
|
if(!CAp::Assert(ArrayResize(s.m_TreeBuf,ntrees)==ntrees,StringFormat("%s: resize buffer failed (%d)",__FUNCTION__,GetLastError())))
|
|
return;
|
|
s.m_WorkBuf[0].m_varpool.Resize(nvars);
|
|
s.m_WorkBuf[0].m_trnset.Resize(trnsize);
|
|
s.m_WorkBuf[0].m_oobset.Resize(npoints-trnsize);
|
|
s.m_WorkBuf[0].m_tmp0i.Resize(npoints);
|
|
s.m_WorkBuf[0].m_tmp1i.Resize(npoints);
|
|
s.m_WorkBuf[0].m_tmp0r.Resize(npoints);
|
|
s.m_WorkBuf[0].m_tmp1r.Resize(npoints);
|
|
s.m_WorkBuf[0].m_tmp2r.Resize(npoints);
|
|
s.m_WorkBuf[0].m_tmp3r.Resize(npoints);
|
|
s.m_WorkBuf[0].m_trnlabelsi.Resize(npoints);
|
|
s.m_WorkBuf[0].m_trnlabelsr.Resize(npoints);
|
|
s.m_WorkBuf[0].m_ooblabelsi.Resize(npoints);
|
|
s.m_WorkBuf[0].m_ooblabelsr.Resize(npoints);
|
|
s.m_WorkBuf[0].m_curvals.Resize(npoints);
|
|
s.m_WorkBuf[0].m_bestvals.Resize(npoints);
|
|
s.m_WorkBuf[0].m_classpriors.Resize(nclasses);
|
|
s.m_WorkBuf[0].m_classtotals0.Resize(nclasses);
|
|
s.m_WorkBuf[0].m_classtotals1.Resize(nclasses);
|
|
s.m_WorkBuf[0].m_classtotals01.Resize(2*nclasses);
|
|
s.m_WorkBuf[0].m_treebuf.Resize(maxtreesize);
|
|
s.m_WorkBuf[0].m_trnsize=trnsize;
|
|
s.m_WorkBuf[0].m_oobsize=npoints-trnsize;
|
|
s.m_VoteBuf[0].m_trntotals=vector<double>::Zeros(npoints*nclasses);
|
|
s.m_VoteBuf[0].m_oobtotals=s.m_VoteBuf[0].m_trntotals;
|
|
s.m_VoteBuf[0].m_trncounts.Resize(npoints);
|
|
s.m_VoteBuf[0].m_oobcounts.Resize(npoints);
|
|
s.m_VoteBuf[0].m_trncounts.Fill(0);
|
|
s.m_VoteBuf[0].m_oobcounts.Fill(0);
|
|
s.m_VoteBuf[0].m_giniimportances=vector<double>::Zeros(nvars);
|
|
for(int treeIdx=1; treeIdx<ntrees; treeIdx++)
|
|
{
|
|
s.m_WorkBuf[treeIdx]=s.m_WorkBuf[0];
|
|
s.m_VoteBuf[treeIdx]=s.m_VoteBuf[0];
|
|
}
|
|
//--- Select session seed (individual trees are constructed using
|
|
//--- combination of session and local seeds).
|
|
sessionseed=s.m_RDFGlobalSeed;
|
|
if(s.m_RDFGlobalSeed<=0)
|
|
sessionseed=CMath::RandomInteger(30000);
|
|
//--- Prepare In-and-Out-of-Bag matrix, if needed
|
|
s.m_NeedIOBMatrix=(s.m_RDFImportance==m_NeedPermutation);
|
|
if(s.m_NeedIOBMatrix)
|
|
{
|
|
//--- Prepare default state of In-and-Out-of-Bag matrix
|
|
s.m_IOBMatrix.Resize(ntrees,npoints);
|
|
s.m_IOBMatrix.Fill((int)false);
|
|
}
|
|
//--- Build tree
|
|
BuildRandomTree(s,0,ntrees);
|
|
//--- Merge tree and output result
|
|
MergeTrees(s,df);
|
|
//--- Process voting results and output training set and OOB errors.
|
|
//--- Finalize tree construction.
|
|
ProcessVotingResults(s,ntrees,buf,rep);
|
|
DFCreateBuffer(df,df.m_Buffer);
|
|
//--- Perform variable importance estimation
|
|
EstimateVariableImportance(s,sessionseed,df,ntrees,rep);
|
|
//--- Update progress counter
|
|
s.m_RDFProgress=s.m_RDFTotal;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs binary compression of the decision forest.|
|
|
//| Original decision forest produced by the forest builder is stored|
|
|
//| using 64-bit representation for all numbers - offsets, variable |
|
|
//| indexes, split points. |
|
|
//| It is possible to significantly reduce model size by means of: |
|
|
//| * using compressed dynamic encoding for integers (offsets and |
|
|
//| variable indexes), which uses just 1 byte to store small ints|
|
|
//| (less than 128), just 2 bytes for larger values (less than |
|
|
//| 128^2) and so on |
|
|
//| * storing floating point numbers using 8-bit exponent and |
|
|
//| 16-bit mantissa |
|
|
//| As result, model needs significantly less memory (compression |
|
|
//| factor depends on variable and class counts). In particular: |
|
|
//| * NVars<128 and NClasses<128 result in 4.4x-5.7x model size |
|
|
//| reduction |
|
|
//| * NVars<16384 and NClasses<128 result in 3.7x-4.5x model size |
|
|
//| reduction |
|
|
//| Such storage format performs lossless compression of all integers|
|
|
//| but compression of floating point values (split values) is lossy,|
|
|
//| with roughly 0.01% relative error introduced during rounding. |
|
|
//| Thus, we recommend you to re-evaluate model accuracy after |
|
|
//| compression. |
|
|
//| Another downside of compression is ~1.5x reduction in the |
|
|
//| inference speed due to necessity of dynamic decompression of the |
|
|
//| compressed model. |
|
|
//| INPUT PARAMETERS: |
|
|
//| DF - decision forest built by forest builder |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| DF - replaced by compressed forest |
|
|
//| RESULT: |
|
|
//| compression factor (in-RAM size of the compressed model vs than |
|
|
//| of the uncompressed one), positive number larger than 1.0 |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::DFBinaryCompression(CDecisionForest &df)
|
|
{
|
|
return(BinaryCompression(df,false));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This is a 8-bit version of DFBinaryCompression. |
|
|
//| Not recommended for external use because it is too lossy. |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::DFBinaryCompression8(CDecisionForest &df)
|
|
{
|
|
return(BinaryCompression(df,true));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFProcess(CDecisionForest &df,double &x[],double &y[])
|
|
{
|
|
CRowDouble X=x;
|
|
CRowDouble Y;
|
|
DFProcess(df,X,Y);
|
|
Y.ToArray(y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFProcess(CDecisionForest &df,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int offs=0;
|
|
int i=0;
|
|
double v=0;
|
|
int treesize=0;
|
|
bool processed=false;
|
|
//--- Proceed
|
|
//--- Although comments above warn you about thread-unsafety of this
|
|
//--- function, it is de facto thread-safe. However, thread safety is
|
|
//--- an accidental side-effect of the specific inference algorithm
|
|
//--- being used. It may disappear in the future versions of the DF
|
|
//--- models, so you should NOT rely on it.
|
|
y.Resize(df.m_NClasses);
|
|
//--- initialization
|
|
y.Fill(0);
|
|
//--- calculation
|
|
if(df.m_ForestFormat==m_DFUncompressedV0)
|
|
{
|
|
//--- Process trees stored in uncompressed format
|
|
for(i=0; i<df.m_NTrees; i++)
|
|
{
|
|
DFProcessInternalUncompressed(df,offs,offs+1,x,y);
|
|
offs+=(int)MathRound(df.m_Trees[offs]);
|
|
}
|
|
processed=true;
|
|
}
|
|
if(df.m_ForestFormat==m_DFCompressedV0)
|
|
{
|
|
//--- Process trees stored in compressed format
|
|
offs=0;
|
|
for(i=0; i<df.m_NTrees; i++)
|
|
{
|
|
treesize=UnstreamUint(df.m_Trees8,offs);
|
|
DFProcessInternalCompressed(df,offs,x,y);
|
|
offs+=treesize;
|
|
}
|
|
processed=true;
|
|
}
|
|
if(!CAp::Assert(processed,__FUNCTION__": integrity check failed (unexpected format?)"))
|
|
return;
|
|
y*=1.0/(double)df.m_NTrees;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| '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. |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFProcessI(CDecisionForest &df,double &x[],double &y[])
|
|
{
|
|
DFProcess(df,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFProcessI(CDecisionForest &df,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
DFProcess(df,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns first component of the inferred vector |
|
|
//| (i.e. one with index #0). |
|
|
//| It is a convenience wrapper for dfprocess() intended for either: |
|
|
//| * 1-dimensional regression problems |
|
|
//| * 2-class classification problems |
|
|
//| In the former case this function returns inference result as |
|
|
//| scalar, which is definitely more convenient that wrapping it as |
|
|
//| vector. In the latter case it returns probability of object |
|
|
//| belonging to class #0. |
|
|
//| If you call it for anything different from two cases above, it |
|
|
//| will work as defined, i.e. return y[0], although it is of less |
|
|
//| use in such cases. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - DF model |
|
|
//| X - input vector, array[0..NVars-1]. |
|
|
//| RESULT: |
|
|
//| Y[0] |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::DFProcess0(CDecisionForest &model,CRowDouble &x)
|
|
{
|
|
//--- copy
|
|
model.m_Buffer.m_x=x;
|
|
//--- function call
|
|
DFProcess(model,model.m_Buffer.m_x,model.m_Buffer.m_y);
|
|
//--- return result
|
|
return(model.m_Buffer.m_y[0]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns most probable class number for an input X. |
|
|
//| It is same as calling DFProcess(model,x,y), then determining |
|
|
//| i=ArgMax(y[i]) and returning i. |
|
|
//| A class number in [0,NOut) range in returned for classification |
|
|
//| problems, -1 is returned when this function is called for |
|
|
//| regression problems. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - decision forest model |
|
|
//| X - input vector, array[0..NVars-1]. |
|
|
//| RESULT: |
|
|
//| class number, -1 for regression tasks |
|
|
//+------------------------------------------------------------------+
|
|
int CDForest::DFClassify(CDecisionForest &model,CRowDouble &x)
|
|
{
|
|
//--- check
|
|
if(model.m_NClasses<2)
|
|
return(-1);
|
|
model.m_Buffer.m_x=x;
|
|
DFProcess(model,model.m_Buffer.m_x,model.m_Buffer.m_y);
|
|
//--- return result
|
|
return (int)model.m_Buffer.m_y.ArgMax();
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::DFRelClsError(CDecisionForest &df,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::DFAvgCE(CDecisionForest &df,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
CRowDouble x;
|
|
CRowDouble y;
|
|
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
x=xy[i]+0;
|
|
x.Resize(df.m_NVars);
|
|
//--- function call
|
|
DFProcess(df,x,y);
|
|
//--- check
|
|
if(df.m_NClasses>1)
|
|
{
|
|
//--- classification-specific code
|
|
int k=(int)MathRound(xy.Get(i,df.m_NVars));
|
|
//--- check
|
|
if(y[k]!=0.0)
|
|
result-=MathLog(y[k]);
|
|
else
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::DFRMSError(CDecisionForest &df,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
CRowDouble x;
|
|
CRowDouble y;
|
|
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
x=xy[i]+0;
|
|
x.Resize(df.m_NVars);
|
|
//--- function call
|
|
DFProcess(df,x,y);
|
|
//--- check
|
|
if(df.m_NClasses>1)
|
|
{
|
|
//--- classification-specific code
|
|
int k=(int)MathRound(xy.Get(i,df.m_NVars));
|
|
y.Add(k,-1);
|
|
result+=MathPow(y.ToVector()+0,2.0).Sum();
|
|
}
|
|
else
|
|
{
|
|
//--- regression-specific code
|
|
result+=CMath::Sqr(y[0]-xy.Get(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. |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::DFAvgError(CDecisionForest &df,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
CRowDouble x;
|
|
CRowDouble y;
|
|
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
//--- copy
|
|
x=xy[i]+0;
|
|
x.Resize(df.m_NVars);
|
|
//--- function call
|
|
DFProcess(df,x,y);
|
|
//--- check
|
|
if(df.m_NClasses>1)
|
|
{
|
|
//--- classification-specific code
|
|
int k=(int)MathRound(xy.Get(i,df.m_NVars));
|
|
y.Add(k,-1);
|
|
result+=(y.Abs()+0).Sum();
|
|
}
|
|
else
|
|
{
|
|
//--- regression-specific code
|
|
result+=MathAbs(y[0]-xy.Get(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. |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::DFAvgRelError(CDecisionForest &df,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int relcnt=0;
|
|
//--- creating arrays
|
|
CRowDouble x;
|
|
CRowDouble y;
|
|
//--- initialization
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
//--- copy
|
|
x=xy[i]+0;
|
|
x.Resize(df.m_NVars);
|
|
//--- function call
|
|
DFProcess(df,x,y);
|
|
//--- check
|
|
if(df.m_NClasses>1)
|
|
{
|
|
//--- classification-specific code
|
|
int k=(int)MathRound(xy.Get(i,df.m_NVars));
|
|
if(k<0 || k>=df.m_NVars)
|
|
continue;
|
|
result+=MathAbs(y[k]-1);
|
|
relcnt++;
|
|
}
|
|
else
|
|
{
|
|
//--- regression-specific code
|
|
if(xy.Get(i,df.m_NVars)!=0.0)
|
|
{
|
|
result+=MathAbs((y[0]-xy.Get(i,df.m_NVars))/xy.Get(i,df.m_NVars));
|
|
relcnt ++;
|
|
}
|
|
}
|
|
}
|
|
//--- check
|
|
if(relcnt>0)
|
|
result=result/relcnt;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copying of DecisionForest strucure |
|
|
//| INPUT PARAMETERS: |
|
|
//| DF1 - original |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| DF2 - copy |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFCopy(CDecisionForest &df1,CDecisionForest &df2)
|
|
{
|
|
if(df1.m_ForestFormat==m_DFUncompressedV0)
|
|
{
|
|
df2.m_ForestFormat=df1.m_ForestFormat;
|
|
df2.m_NVars=df1.m_NVars;
|
|
df2.m_NClasses=df1.m_NClasses;
|
|
df2.m_NTrees=df1.m_NTrees;
|
|
df2.m_BufSize=df1.m_BufSize ;
|
|
df2.m_Trees=df1.m_Trees;
|
|
DFCreateBuffer(df2,df2.m_Buffer);
|
|
return;
|
|
}
|
|
if(df1.m_ForestFormat==m_DFCompressedV0)
|
|
{
|
|
df2.m_ForestFormat=df1.m_ForestFormat;
|
|
df2.m_NVars=df1.m_NVars;
|
|
df2.m_NClasses=df1.m_NClasses;
|
|
df2.m_NTrees=df1.m_NTrees;
|
|
df2.m_BufSize=df1.m_BufSize ;
|
|
df2.m_UseMantissa8=df1.m_UseMantissa8;
|
|
df2.m_Trees8=df1.m_Trees8;
|
|
DFCreateBuffer(df2,df2.m_Buffer);
|
|
return;
|
|
}
|
|
CAp::Assert(false,__FUNCTION__": unexpected forest format");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: allocation |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFAlloc(CSerializer &s,CDecisionForest &forest)
|
|
{
|
|
//--- preparation to serialize
|
|
if(forest.m_ForestFormat==m_DFUncompressedV0)
|
|
{
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
CApServ::AllocRealArray(s,forest.m_Trees,forest.m_BufSize);
|
|
return;
|
|
}
|
|
if(forest.m_ForestFormat==m_DFCompressedV0)
|
|
{
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
CApServ::AllocIntegerArray(s,forest.m_Trees8,forest.m_BufSize);
|
|
return;
|
|
}
|
|
CAp::Assert(false,__FUNCTION__": unexpected forest format");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: serialization |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFSerialize(CSerializer &s,CDecisionForest &forest)
|
|
{
|
|
//--- serializetion
|
|
if(forest.m_ForestFormat==m_DFUncompressedV0)
|
|
{
|
|
s.Serialize_Int(CSCodes::GetRDFSerializationCode());
|
|
s.Serialize_Int(m_DFUncompressedV0);
|
|
s.Serialize_Int(forest.m_NVars);
|
|
s.Serialize_Int(forest.m_NClasses);
|
|
s.Serialize_Int(forest.m_NTrees);
|
|
s.Serialize_Int(forest.m_BufSize);
|
|
CApServ::SerializeRealArray(s,forest.m_Trees,forest.m_BufSize);
|
|
return;
|
|
}
|
|
if(forest.m_ForestFormat==m_DFCompressedV0)
|
|
{
|
|
s.Serialize_Int(CSCodes::GetRDFSerializationCode());
|
|
s.Serialize_Int(forest.m_ForestFormat);
|
|
s.Serialize_Bool(forest.m_UseMantissa8);
|
|
s.Serialize_Int(forest.m_NVars);
|
|
s.Serialize_Int(forest.m_NClasses);
|
|
s.Serialize_Int(forest.m_NTrees);
|
|
CApServ::SerializeIntegerArray(s,forest.m_Trees8,forest.m_Trees8.Size());
|
|
return;
|
|
}
|
|
CAp::Assert(false,__FUNCTION__": unexpected forest format");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: unserialization |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFUnserialize(CSerializer &s,CDecisionForest &forest)
|
|
{
|
|
bool processed=false;
|
|
//--- check correctness of header
|
|
int i0=s.Unserialize_Int();
|
|
if(!CAp::Assert(i0==CSCodes::GetRDFSerializationCode(),__FUNCTION__": stream header corrupted"))
|
|
return;
|
|
//--- Read forest
|
|
int forestformat=s.Unserialize_Int();
|
|
if(forestformat==m_DFUncompressedV0)
|
|
{
|
|
//--- Unserialize data
|
|
forest.m_ForestFormat=forestformat;
|
|
forest.m_NVars=s.Unserialize_Int();
|
|
forest.m_NClasses=s.Unserialize_Int();
|
|
forest.m_NTrees=s.Unserialize_Int();
|
|
forest.m_BufSize=s.Unserialize_Int();
|
|
CApServ::UnserializeRealArray(s,forest.m_Trees);
|
|
processed=true;
|
|
}
|
|
if(forestformat==m_DFCompressedV0)
|
|
{
|
|
//--- Unserialize data
|
|
forest.m_ForestFormat=forestformat;
|
|
forest.m_UseMantissa8=s.Unserialize_Bool();
|
|
forest.m_NVars=s.Unserialize_Int();
|
|
forest.m_NClasses=s.Unserialize_Int();
|
|
forest.m_NTrees=s.Unserialize_Int();
|
|
CApServ::UnserializeIntegerArray(s,forest.m_Trees8);
|
|
processed=true;
|
|
}
|
|
if(!CAp::Assert(processed,__FUNCTION__": unexpected forest format"))
|
|
return;
|
|
//--- Prepare buffer
|
|
DFCreateBuffer(forest,forest.m_Buffer);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds random decision forest. |
|
|
//| ---- DEPRECATED VERSION! USE DECISION FOREST BUILDER OBJECT ---- |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuildRandomDecisionForest(CMatrixDouble &xy,
|
|
int npoints,
|
|
int nvars,
|
|
int nclasses,
|
|
int ntrees,
|
|
double r,
|
|
int &info,
|
|
CDecisionForest &df,
|
|
CDFReport &rep)
|
|
{
|
|
info=0;
|
|
//--- check
|
|
if(r<=0.0 || r>1.0)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
|
|
int samplesize=MathMax((int)MathRound(r*npoints),1);
|
|
DFBuildInternal(xy,npoints,nvars,nclasses,ntrees,samplesize,MathMax(nvars/2,1),m_DFUseStrongSplits+m_DFUseEVS,info,df,rep);
|
|
}
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds random decision forest. |
|
|
//| ---- DEPRECATED VERSION! USE DECISION FOREST BUILDER OBJECT ---- |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuildRandomDecisionForestX1(CMatrixDouble &xy,
|
|
int npoints,
|
|
int nvars,
|
|
int nclasses,
|
|
int ntrees,
|
|
int nrndvars,
|
|
double r,
|
|
int &info,
|
|
CDecisionForest &df,
|
|
CDFReport &rep)
|
|
{
|
|
info=0;
|
|
//--- check
|
|
if(r<=0.0 || r>1.0)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
if(nrndvars<=0 || nrndvars>nvars)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
|
|
int samplesize=MathMax((int)MathRound(r*npoints),1);
|
|
DFBuildInternal(xy,npoints,nvars,nclasses,ntrees,samplesize,nrndvars,m_DFUseStrongSplits+m_DFUseEVS,info,df,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFBuildInternal(CMatrixDouble &xy,
|
|
int npoints,
|
|
int nvars,
|
|
int nclasses,
|
|
int ntrees,
|
|
int samplesize,
|
|
int nfeatures,
|
|
int flags,
|
|
int &info,
|
|
CDecisionForest &df,
|
|
CDFReport &rep)
|
|
{
|
|
CDecisionForestBuilder builder;
|
|
info=0;
|
|
//--- Test for inputs
|
|
if(npoints<1 || samplesize<1 || samplesize>npoints || nvars<1 || nclasses<1 || ntrees<1 || nfeatures<1)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
if(nclasses>1)
|
|
for(int i=0; i<=npoints-1; i++)
|
|
if(MathRound(xy.Get(i,nvars))<0 || MathRound(xy.Get(i,nvars))>=nclasses)
|
|
{
|
|
info=-2;
|
|
return;
|
|
}
|
|
|
|
info=1;
|
|
DFBuilderCreate(builder);
|
|
DFBuilderSetDataset(builder,xy,npoints,nvars,nclasses);
|
|
DFBuilderSetSubsampleRatio(builder,(double)samplesize/(double)npoints);
|
|
DFBuilderSetRndVars(builder,nfeatures);
|
|
DFBuilderBuildRandomForest(builder,ntrees,df,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Builds a range of random trees [TreeIdx0, TreeIdx1) using |
|
|
//| decision forest algorithm. Tree index is used to seed per - tree |
|
|
//| RNG. |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::BuildRandomTree(CDecisionForestBuilder &s,int treeidx0,
|
|
int treeidx1)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int npoints=0;
|
|
int nvars=0;
|
|
int nclasses=0;
|
|
CHighQualityRandState rs;
|
|
int treesize=0;
|
|
int varstoselect=0;
|
|
int workingsetsize=0;
|
|
double meanloss=0;
|
|
//--- Prepare
|
|
npoints=s.m_NPoints;
|
|
nvars=s.m_NVars;
|
|
nclasses=s.m_NClasses;
|
|
//---
|
|
if(treeidx0>treeidx1)
|
|
CApServ::Swap(treeidx0,treeidx1);
|
|
if(treeidx0==treeidx1)
|
|
treeidx1++;
|
|
for(int treeidx=treeidx0; treeidx<treeidx1; treeidx++)
|
|
{
|
|
if(s.m_RDFGlobalSeed>0)
|
|
{
|
|
CHighQualityRand::HQRndSeed(s.m_RDFGlobalSeed,1+treeidx,rs);
|
|
}
|
|
else
|
|
{
|
|
CHighQualityRand::HQRndSeed(CMath::RandomInteger(30000),1+treeidx,rs);
|
|
}
|
|
//--- Prepare everything for tree construction.
|
|
if(!CAp::Assert(s.m_WorkBuf[treeidx].m_trnsize>=1,__FUNCTION__": integrity check failed (34636)"))
|
|
return;
|
|
if(!CAp::Assert(s.m_WorkBuf[treeidx].m_oobsize>=0,__FUNCTION__": integrity check failed (45745)"))
|
|
return;
|
|
if(!CAp::Assert(s.m_WorkBuf[treeidx].m_trnsize+s.m_WorkBuf[treeidx].m_oobsize==npoints,__FUNCTION__": integrity check failed (89415)"))
|
|
return;
|
|
workingsetsize=-1;
|
|
s.m_WorkBuf[treeidx].m_varpoolsize=0;
|
|
for(i=0; i<nvars; i++)
|
|
if(s.m_DSMin[i]!=s.m_DSMax[i])
|
|
{
|
|
s.m_WorkBuf[treeidx].m_varpool.Set(s.m_WorkBuf[treeidx].m_varpoolsize,i);
|
|
s.m_WorkBuf[treeidx].m_varpoolsize++;
|
|
}
|
|
workingsetsize=s.m_WorkBuf[treeidx].m_varpoolsize;
|
|
if(!CAp::Assert(workingsetsize>=0,__FUNCTION__": integrity check failed (73f5)"))
|
|
return;
|
|
for(i=0; i<npoints; i++)
|
|
s.m_WorkBuf[treeidx].m_tmp0i.Set(i,i);
|
|
for(i=0; i<s.m_WorkBuf[treeidx].m_trnsize; i++)
|
|
{
|
|
j=CHighQualityRand::HQRndUniformI(rs,npoints-i);
|
|
s.m_WorkBuf[treeidx].m_tmp0i.Swap(i,i+j);
|
|
s.m_WorkBuf[treeidx].m_trnset.Set(i,s.m_WorkBuf[treeidx].m_tmp0i[i]);
|
|
if(nclasses>1)
|
|
s.m_WorkBuf[treeidx].m_trnlabelsi.Set(i,s.m_DSIVal[s.m_WorkBuf[treeidx].m_tmp0i[i]]);
|
|
else
|
|
s.m_WorkBuf[treeidx].m_trnlabelsr.Set(i,s.m_DSRVal[s.m_WorkBuf[treeidx].m_tmp0i[i]]);
|
|
if(s.m_NeedIOBMatrix)
|
|
s.m_IOBMatrix.Set(treeidx,s.m_WorkBuf[treeidx].m_trnset[i],(int)true);
|
|
}
|
|
for(i=0; i<s.m_WorkBuf[treeidx].m_oobsize; i++)
|
|
{
|
|
j=s.m_WorkBuf[treeidx].m_tmp0i[s.m_WorkBuf[treeidx].m_trnsize+i];
|
|
s.m_WorkBuf[treeidx].m_oobset.Set(i,j);
|
|
if(nclasses>1)
|
|
s.m_WorkBuf[treeidx].m_ooblabelsi.Set(i,s.m_DSIVal[j]);
|
|
else
|
|
s.m_WorkBuf[treeidx].m_ooblabelsr.Set(i,s.m_DSRVal[j]);
|
|
}
|
|
varstoselect=(int)MathRound(MathSqrt(nvars));
|
|
if(s.m_RDFVars>0.0)
|
|
varstoselect=(int)MathRound(s.m_RDFVars);
|
|
else
|
|
if(s.m_RDFVars<0.0)
|
|
{
|
|
varstoselect=(int)MathRound(-(nvars*s.m_RDFVars));
|
|
}
|
|
varstoselect=MathMax(varstoselect,1);
|
|
varstoselect=MathMin(varstoselect,nvars);
|
|
//--- Perform recurrent construction
|
|
if(s.m_RDFImportance==m_NeedTrnGini)
|
|
meanloss=MeanNRMS2(nclasses,s.m_WorkBuf[treeidx].m_trnlabelsi,s.m_WorkBuf[treeidx].m_trnlabelsr,0,s.m_WorkBuf[treeidx].m_trnsize,s.m_WorkBuf[treeidx].m_trnlabelsi,s.m_WorkBuf[treeidx].m_trnlabelsr,0,s.m_WorkBuf[treeidx].m_trnsize,s.m_WorkBuf[treeidx].m_tmpnrms2);
|
|
else
|
|
meanloss=MeanNRMS2(nclasses,s.m_WorkBuf[treeidx].m_trnlabelsi,s.m_WorkBuf[treeidx].m_trnlabelsr,0,s.m_WorkBuf[treeidx].m_trnsize,s.m_WorkBuf[treeidx].m_ooblabelsi,s.m_WorkBuf[treeidx].m_ooblabelsr,0,s.m_WorkBuf[treeidx].m_oobsize,s.m_WorkBuf[treeidx].m_tmpnrms2);
|
|
treesize=1;
|
|
BuildRandomTreeRec(s,s.m_WorkBuf[treeidx],workingsetsize,varstoselect,s.m_WorkBuf[treeidx].m_treebuf,s.m_VoteBuf[treeidx],rs,0,s.m_WorkBuf[treeidx].m_trnsize,0,s.m_WorkBuf[treeidx].m_oobsize,meanloss,meanloss,treesize);
|
|
s.m_WorkBuf[treeidx].m_treebuf.Set(0,treesize);
|
|
//--- Store tree
|
|
s.m_TreeBuf[treeidx].m_treebuf=s.m_WorkBuf[treeidx].m_treebuf;
|
|
s.m_TreeBuf[treeidx].m_treeidx=treeidx;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Recurrent tree construction function using caller - allocated |
|
|
//| buffers and caller - initialized RNG. |
|
|
//| Following iterms are processed: |
|
|
//| * items [Idx0, Idx1) of WorkBuf.TrnSet |
|
|
//| * items [OOBIdx0, OOBIdx1) of WorkBuf.OOBSet |
|
|
//| TreeSize on input must be 1(header element of the tree), on |
|
|
//| output it contains size of the tree. |
|
|
//| OOBLoss on input must contain value of MeanNRMS2(...) computed |
|
|
//| for entire dataset. |
|
|
//| Variables from #0 to #WorkingSet - 1 from WorkBuf.VarPool are |
|
|
//| used(for block algorithm: blocks, not vars) |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::BuildRandomTreeRec(CDecisionForestBuilder &s,
|
|
CDFWorkBuf &workbuf,
|
|
int workingset,
|
|
int varstoselect,
|
|
CRowDouble &treebuf,
|
|
CDFVoteBuf &votebuf,
|
|
CHighQualityRandState &rs,
|
|
int idx0,
|
|
int idx1,
|
|
int oobidx0,
|
|
int oobidx1,
|
|
double meanloss,
|
|
double topmostmeanloss,
|
|
int &treesize)
|
|
{
|
|
//---create variables
|
|
int npoints=0;
|
|
int nclasses=0;
|
|
int i=0;
|
|
int j=0;
|
|
int j0=0;
|
|
double v=0;
|
|
bool labelsaresame;
|
|
int offs=0;
|
|
int varbest=0;
|
|
double splitbest=0;
|
|
int i1=0;
|
|
int i2=0;
|
|
int idxtrn=0;
|
|
int idxoob=0;
|
|
double meanloss0=0;
|
|
double meanloss1=0;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_DSType==0,__FUNCTION__": not supported skbdgfsi!"))
|
|
return;
|
|
if(!CAp::Assert(idx0<idx1,__FUNCTION__": integrity check failed (3445)"))
|
|
return;
|
|
if(!CAp::Assert(oobidx0<=oobidx1,__FUNCTION__": integrity check failed (7452)"))
|
|
return;
|
|
|
|
npoints=s.m_NPoints;
|
|
nclasses=s.m_NClasses;
|
|
//--- Check labels: all same or not?
|
|
if(nclasses>1)
|
|
{
|
|
labelsaresame=true;
|
|
workbuf.m_classpriors.Fill(0);
|
|
j0=workbuf.m_trnlabelsi[idx0];
|
|
for(i=idx0; i<idx1; i++)
|
|
{
|
|
j=workbuf.m_trnlabelsi[i];
|
|
workbuf.m_classpriors.Add(j,1);
|
|
labelsaresame=labelsaresame && j0==j;
|
|
}
|
|
}
|
|
else
|
|
labelsaresame=false;
|
|
//--- Leaf node
|
|
if(idx1-idx0==1 || labelsaresame)
|
|
{
|
|
if(nclasses==1)
|
|
OutputLeaf(s,workbuf,treebuf,votebuf,idx0,idx1,oobidx0,oobidx1,treesize,workbuf.m_trnlabelsr[idx0]);
|
|
else
|
|
OutputLeaf(s,workbuf,treebuf,votebuf,idx0,idx1,oobidx0,oobidx1,treesize,workbuf.m_trnlabelsi[idx0]);
|
|
return;
|
|
}
|
|
//--- Non-leaf node.
|
|
//--- Investigate possible splits.
|
|
if(!CAp::Assert(s.m_RDFAlgo==0,__FUNCTION__": unexpected algo"))
|
|
return;
|
|
ChooseCurrentSplitDense(s,workbuf,workingset,varstoselect,rs,idx0,idx1,varbest,splitbest);
|
|
if(varbest<0)
|
|
{
|
|
//--- No good split was found; make leaf (label is randomly chosen) and exit.
|
|
if(nclasses>1)
|
|
v=workbuf.m_trnlabelsi[idx0+CHighQualityRand::HQRndUniformI(rs,idx1-idx0)];
|
|
else
|
|
v=workbuf.m_trnlabelsr[idx0+CHighQualityRand::HQRndUniformI(rs,idx1-idx0)];
|
|
OutputLeaf(s,workbuf,treebuf,votebuf,idx0,idx1,oobidx0,oobidx1,treesize,v);
|
|
return;
|
|
}
|
|
//--- Good split WAS found, we can perform it:
|
|
//--- * first, we split training set
|
|
//--- * then, we similarly split OOB set
|
|
if(!CAp::Assert(s.m_DSType==0,__FUNCTION__": not supported 54bfdh"))
|
|
return;
|
|
offs=npoints*varbest;
|
|
i1=idx0;
|
|
i2=idx1-1;
|
|
while(i1<=i2)
|
|
{
|
|
//--- Reorder indexes so that left partition is in [Idx0..I1),
|
|
//--- and right partition is in [I2+1..Idx1)
|
|
if(workbuf.m_bestvals[i1]<splitbest)
|
|
{
|
|
i1 ++;
|
|
continue;
|
|
}
|
|
if(workbuf.m_bestvals[i2]>=splitbest)
|
|
{
|
|
i2--;
|
|
continue;
|
|
}
|
|
workbuf.m_trnset.Swap(i1,i2);
|
|
if(nclasses>1)
|
|
workbuf.m_trnlabelsi.Swap(i1,i2);
|
|
else
|
|
workbuf.m_trnlabelsr.Swap(i1,i2);
|
|
i1++;
|
|
i2--;
|
|
}
|
|
if(!CAp::Assert(i1==i2+1,__FUNCTION__": integrity check failed (45rds3)"))
|
|
return;
|
|
idxtrn=i1;
|
|
if(oobidx0<oobidx1)
|
|
{
|
|
//--- Unlike the training subset, the out-of-bag subset corresponding to the
|
|
//--- current sequence of decisions can be empty; thus, we have to explicitly
|
|
//--- handle situation of zero OOB subset.
|
|
i1=oobidx0;
|
|
i2=oobidx1-1;
|
|
while(i1<=i2)
|
|
{
|
|
//--- Reorder indexes so that left partition is in [Idx0..I1),
|
|
//--- and right partition is in [I2+1..Idx1)
|
|
if(s.m_DSData[offs+workbuf.m_oobset[i1]]<splitbest)
|
|
{
|
|
i1++;
|
|
continue;
|
|
}
|
|
if(s.m_DSData[offs+workbuf.m_oobset[i2]]>=splitbest)
|
|
{
|
|
i2--;
|
|
continue;
|
|
}
|
|
workbuf.m_oobset.Swap(i1,i2);
|
|
if(nclasses>1)
|
|
workbuf.m_ooblabelsi.Swap(i1,i2);
|
|
else
|
|
workbuf.m_ooblabelsr.Swap(i1,i2);
|
|
i1++;
|
|
i2--;
|
|
}
|
|
if(!CAp::Assert(i1==i2+1,__FUNCTION__": integrity check failed (643fs3)"))
|
|
return;
|
|
idxoob=i1;
|
|
}
|
|
else
|
|
idxoob=oobidx0;
|
|
//--- Compute estimates of NRMS2 loss over TRN or OOB subsets, update Gini importances
|
|
if(s.m_RDFImportance==m_NeedTrnGini)
|
|
{
|
|
meanloss0=MeanNRMS2(nclasses,workbuf.m_trnlabelsi,workbuf.m_trnlabelsr,idx0,idxtrn,workbuf.m_trnlabelsi,workbuf.m_trnlabelsr,idx0,idxtrn,workbuf.m_tmpnrms2);
|
|
meanloss1=MeanNRMS2(nclasses,workbuf.m_trnlabelsi,workbuf.m_trnlabelsr,idxtrn,idx1,workbuf.m_trnlabelsi,workbuf.m_trnlabelsr,idxtrn,idx1,workbuf.m_tmpnrms2);
|
|
}
|
|
else
|
|
{
|
|
meanloss0=MeanNRMS2(nclasses,workbuf.m_trnlabelsi,workbuf.m_trnlabelsr,idx0,idxtrn,workbuf.m_ooblabelsi,workbuf.m_ooblabelsr,oobidx0,idxoob,workbuf.m_tmpnrms2);
|
|
meanloss1=MeanNRMS2(nclasses,workbuf.m_trnlabelsi,workbuf.m_trnlabelsr,idxtrn,idx1,workbuf.m_ooblabelsi,workbuf.m_ooblabelsr,idxoob,oobidx1,workbuf.m_tmpnrms2);
|
|
}
|
|
votebuf.m_giniimportances.Add(varbest,(meanloss-(meanloss0+meanloss1))/(topmostmeanloss+1.0e-20));
|
|
//--- Generate tree node and subtrees (recursively)
|
|
treebuf.Set(treesize,varbest);
|
|
treebuf.Set(treesize+1,splitbest);
|
|
i=treesize;
|
|
treesize=treesize+m_InnerNodeWidth;
|
|
BuildRandomTreeRec(s,workbuf,workingset,varstoselect,treebuf,votebuf,rs,idx0,idxtrn,oobidx0,idxoob,meanloss0,topmostmeanloss,treesize);
|
|
treebuf.Set(i+2,treesize);
|
|
BuildRandomTreeRec(s,workbuf,workingset,varstoselect,treebuf,votebuf,rs,idxtrn,idx1,idxoob,oobidx1,meanloss1,topmostmeanloss,treesize);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Estimates permutation variable importance ratings for a range of |
|
|
//| dataset points. |
|
|
//| Initial call to this function should span entire range of the |
|
|
//| dataset, [Idx0, Idx1) = [0, NPoints), because function performs |
|
|
//| initialization of some internal structures when called with these|
|
|
//| arguments. |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::EstimateVariableImportance(CDecisionForestBuilder &s,
|
|
int sessionseed,
|
|
CDecisionForest &df,
|
|
int ntrees,
|
|
CDFReport &rep)
|
|
{
|
|
//--- create variables
|
|
int npoints=0;
|
|
int nvars=0;
|
|
int nclasses=0;
|
|
int nperm=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
CRowDouble tmpr0;
|
|
CRowDouble tmpr1;
|
|
CRowInt tmpi0;
|
|
vector<double> losses;
|
|
CDFPermimpBuf permseed;
|
|
double nopermloss=0;
|
|
double totalpermloss=0;
|
|
CHighQualityRandState varimprs;
|
|
npoints=s.m_NPoints;
|
|
nvars=s.m_NVars;
|
|
nclasses=s.m_NClasses;
|
|
//--- No importance rating
|
|
if(s.m_RDFImportance==0)
|
|
return;
|
|
//--- Gini importance
|
|
if(s.m_RDFImportance==m_NeedTrnGini || s.m_RDFImportance==m_NeedOOBGini)
|
|
{
|
|
//--- Merge OOB Gini importances computed during tree generation
|
|
int total=ArraySize(s.m_VoteBuf);
|
|
losses=s.m_VoteBuf[0].m_giniimportances.ToVector();
|
|
for(int vote=1; vote<total; vote++)
|
|
losses+=s.m_VoteBuf[vote].m_giniimportances.ToVector();
|
|
losses.Resize(nvars);
|
|
losses=losses/(double)ntrees;
|
|
losses.Clip(0,1);
|
|
rep.m_varimportances=losses;
|
|
//--- Compute topvars[] array
|
|
tmpr0=losses*(-1);
|
|
for(i=0; i<nvars; i++)
|
|
rep.m_topvars.Set(i,i);
|
|
CTSort::TagSortFastI(tmpr0,rep.m_topvars,tmpr1,tmpi0,nvars);
|
|
return;
|
|
}
|
|
//--- Permutation importance
|
|
if(s.m_RDFImportance==m_NeedPermutation)
|
|
{
|
|
if(!CAp::Assert(df.m_ForestFormat==m_DFUncompressedV0,__FUNCTION__": integrity check failed (ff)"))
|
|
return;
|
|
if(!CAp::Assert(s.m_IOBMatrix.Rows()>=ntrees && s.m_IOBMatrix.Cols()>=npoints,__FUNCTION__": integrity check failed (IOB)"))
|
|
return;
|
|
//--- Generate packed representation of the shuffle which is applied to all variables
|
|
//--- Ideally we want to apply different permutations to different variables,
|
|
//--- i.e. we have to generate and store NPoints*NVars random numbers.
|
|
//--- However due to performance and memory restrictions we prefer to use compact
|
|
//--- representation:
|
|
//---*we store one "reference" permutation P_ref in VarImpShuffle2[0:NPoints-1]
|
|
//--- * a permutation P_j applied to variable J is obtained by circularly shifting
|
|
//--- elements in P_ref by VarImpShuffle2[NPoints+J]
|
|
CHighQualityRand::HQRndSeed(sessionseed,1117,varimprs);
|
|
s.m_VarImpShuffle2.Resize(npoints+nvars);
|
|
for(i=0; i<npoints; i++)
|
|
s.m_VarImpShuffle2.Set(i,i);
|
|
for(i=0; i<npoints-1; i++)
|
|
{
|
|
j=i+CHighQualityRand::HQRndUniformI(varimprs,npoints-i);
|
|
s.m_VarImpShuffle2.Swap(i,j);
|
|
}
|
|
for(i=0; i<nvars; i++)
|
|
s.m_VarImpShuffle2.Set(npoints+i,CHighQualityRand::HQRndUniformI(varimprs,npoints));
|
|
//--- Prepare buffer object, seed pool
|
|
nperm=nvars+2;
|
|
permseed.m_losses=vector<double>::Zeros(nperm);
|
|
permseed.m_yv.Resize(nperm*nclasses);
|
|
permseed.m_xraw.Resize(nvars);
|
|
permseed.m_xdist.Resize(nvars);
|
|
permseed.m_xcur.Resize(nvars);
|
|
permseed.m_targety.Resize(nclasses);
|
|
permseed.m_startnodes.Resize(nvars);
|
|
permseed.m_y.Resize(nclasses);
|
|
//--- Recursively split subset
|
|
EstimatePermutationImportances(s,df,ntrees,permseed,0,npoints);
|
|
//--- Merge results
|
|
losses=permseed.m_losses+vector<double>::Full(nperm,1.0e-20);
|
|
//--- Compute importances
|
|
nopermloss=losses[nvars+1];
|
|
totalpermloss=losses[nvars];
|
|
losses= losses/totalpermloss-nopermloss/totalpermloss;
|
|
losses.Clip(0,1);
|
|
losses.Resize(nvars);
|
|
rep.m_varimportances=losses;
|
|
//--- Compute topvars[] array
|
|
tmpr0=losses*(-1);
|
|
rep.m_topvars.Resize(nvars);
|
|
for(j=0; j<nvars; j++)
|
|
rep.m_topvars.Set(j,j);
|
|
CTSort::TagSortFastI(tmpr0,rep.m_topvars,tmpr1,tmpi0,nvars);
|
|
return;
|
|
}
|
|
CAp::Assert(false,__FUNCTION__": unexpected importance type");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Estimates permutation variable importance ratings for a range of |
|
|
//| dataset points. |
|
|
//| Initial call to this function should span entire range of the |
|
|
//| dataset, [Idx0, Idx1) = [0, NPoints), because function performs |
|
|
//| initialization of some internal structures when called with these|
|
|
//| arguments. |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::EstimatePermutationImportances(CDecisionForestBuilder &s,
|
|
CDecisionForest &df,
|
|
int ntrees,
|
|
CDFPermimpBuf &permimpbuf,
|
|
int idx0,
|
|
int idx1)
|
|
{
|
|
//--- create variables
|
|
int nperm=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double v=0;
|
|
int treeroot=0;
|
|
int nodeoffs=0;
|
|
double prediction=0;
|
|
int varidx=0;
|
|
int oobcounts=0;
|
|
int srcidx=0;
|
|
int npoints=s.m_NPoints;
|
|
int nvars=s.m_NVars;
|
|
int nclasses=s.m_NClasses;
|
|
//--- check
|
|
if(!CAp::Assert(df.m_ForestFormat==m_DFUncompressedV0,__FUNCTION__": integrity check failed (ff)"))
|
|
return;
|
|
if(!CAp::Assert(idx0>=0 && idx0<=idx1 && idx1<=npoints,__FUNCTION__": integrity check failed (idx)"))
|
|
return;
|
|
if(!CAp::Assert(s.m_IOBMatrix.Rows()>=ntrees && s.m_IOBMatrix.Cols()>=npoints,__FUNCTION__": integrity check failed (IOB)"))
|
|
return;
|
|
//--- main loop
|
|
nperm=nvars+2;
|
|
//--- Process range of points [idx0,idx1)
|
|
for(i=idx0; i<idx1-1; i++)
|
|
{
|
|
if(!CAp::Assert(s.m_DSType==0,__FUNCTION__": unexpected dataset type"))
|
|
return;
|
|
for(j=0; j<nvars; j++)
|
|
{
|
|
permimpbuf.m_xraw.Set(j,s.m_DSData[j*npoints+i]);
|
|
srcidx=s.m_VarImpShuffle2[(i+s.m_VarImpShuffle2[npoints+j])%npoints];
|
|
permimpbuf.m_xdist.Set(j,s.m_DSData[j*npoints+srcidx]);
|
|
}
|
|
if(nclasses>1)
|
|
{
|
|
permimpbuf.m_targety=vector<double>::Zeros(nclasses);
|
|
permimpbuf.m_targety.Set(s.m_DSIVal[i],1);
|
|
}
|
|
else
|
|
permimpbuf.m_targety.Set(0,s.m_DSRVal[i]);
|
|
//--- Process all trees, for each tree compute NPerm losses corresponding
|
|
//--- to various permutations of variable values
|
|
permimpbuf.m_yv=vector<double>::Zeros(nperm*nclasses);
|
|
oobcounts=0;
|
|
treeroot=0;
|
|
for(k=0; k<ntrees; k++)
|
|
{
|
|
if(!s.m_IOBMatrix.Get(k,i))
|
|
{
|
|
//--- Process original (unperturbed) point and analyze path from the
|
|
//--- tree root to the final leaf. Output prediction to RawPrediction.
|
|
//--- Additionally, for each variable in [0,NVars-1] save offset of
|
|
//--- the first split on this variable. It allows us to quickly compute
|
|
//--- tree decision when perturbation does not change decision path.
|
|
if(!CAp::Assert(df.m_ForestFormat==m_DFUncompressedV0,__FUNCTION__": integrity check failed (ff)"))
|
|
return;
|
|
nodeoffs=treeroot+1;
|
|
permimpbuf.m_startnodes.Fill(-1);
|
|
prediction=0;
|
|
while(true)
|
|
{
|
|
if(df.m_Trees[nodeoffs]==-1.0)
|
|
{
|
|
prediction=df.m_Trees[nodeoffs+1];
|
|
break;
|
|
}
|
|
j=(int)MathRound(df.m_Trees[nodeoffs]);
|
|
if(permimpbuf.m_startnodes[j]<0)
|
|
permimpbuf.m_startnodes.Set(j,nodeoffs);
|
|
if(permimpbuf.m_xraw[j]<df.m_Trees[nodeoffs+1])
|
|
nodeoffs+=m_InnerNodeWidth;
|
|
else
|
|
nodeoffs=treeroot+(int)MathRound(df.m_Trees[nodeoffs+2]);
|
|
}
|
|
//--- Save loss for unperturbed point
|
|
varidx=nvars+1;
|
|
if(nclasses>1)
|
|
{
|
|
j=(int)MathRound(prediction);
|
|
permimpbuf.m_yv.Add(varidx*nclasses+j,1);
|
|
}
|
|
else
|
|
permimpbuf.m_yv.Add(varidx,prediction);
|
|
//--- Save loss for all variables being perturbed (XDist).
|
|
//--- This loss is used as a reference loss when we compute R-squared.
|
|
varidx=nvars;
|
|
permimpbuf.m_y=vector<double>::Zeros(nclasses);
|
|
DFProcessInternalUncompressed(df,treeroot,treeroot+1,permimpbuf.m_xdist,permimpbuf.m_y);
|
|
permimpbuf.m_yv.Add(varidx*nclasses+j,permimpbuf.m_y[j]);
|
|
//--- Compute losses for variable #VarIdx being perturbed. Quite an often decision
|
|
//--- process does not actually depend on the variable #VarIdx (path from the tree
|
|
//--- root does not include splits on this variable). In such cases we perform
|
|
//--- quick exit from the loop with precomputed value.
|
|
permimpbuf.m_xcur=permimpbuf.m_xraw;
|
|
for(varidx=0; varidx<nvars; varidx++)
|
|
{
|
|
if(permimpbuf.m_startnodes[varidx]>=0)
|
|
{
|
|
//--- Path from tree root to the final leaf involves split on variable #VarIdx.
|
|
//--- Restart computation from the position first split on #VarIdx.
|
|
if(!CAp::Assert(df.m_ForestFormat==m_DFUncompressedV0,__FUNCTION__": integrity check failed (ff)"))
|
|
return;
|
|
permimpbuf.m_xcur.Set(varidx,permimpbuf.m_xdist[varidx]);
|
|
nodeoffs=permimpbuf.m_startnodes[varidx];
|
|
while(true)
|
|
{
|
|
if(df.m_Trees[nodeoffs]==-1.0)
|
|
{
|
|
if(nclasses>1)
|
|
{
|
|
j=(int)MathRound(df.m_Trees[nodeoffs+1]);
|
|
permimpbuf.m_yv.Add(varidx*nclasses+j,1.0);
|
|
}
|
|
else
|
|
permimpbuf.m_yv.Add(varidx,df.m_Trees[nodeoffs+1]);
|
|
break;
|
|
}
|
|
j=(int)MathRound(df.m_Trees[nodeoffs]);
|
|
if(permimpbuf.m_xcur[j]<df.m_Trees[nodeoffs+1])
|
|
nodeoffs+=m_InnerNodeWidth;
|
|
else
|
|
nodeoffs=treeroot+(int)MathRound(df.m_Trees[nodeoffs+2]);
|
|
}
|
|
permimpbuf.m_xcur.Set(varidx,permimpbuf.m_xraw[varidx]);
|
|
}
|
|
else
|
|
{
|
|
//--- Path from tree root to the final leaf does NOT involve split on variable #VarIdx.
|
|
//--- Permutation does not change tree output, reuse already computed value.
|
|
if(nclasses>1)
|
|
{
|
|
j=(int)MathRound(prediction);
|
|
permimpbuf.m_yv.Add(varidx*nclasses+j,1.0);
|
|
}
|
|
else
|
|
permimpbuf.m_yv.Add(varidx,prediction);
|
|
}
|
|
}
|
|
//--- update OOB counter
|
|
oobcounts++;
|
|
}
|
|
treeroot+=(int)MathRound(df.m_Trees[treeroot]);
|
|
}
|
|
//--- Now YV[] stores NPerm versions of the forest output for various permutations of variable values.
|
|
//--- Update losses.
|
|
for(j=0; j<nperm; j++)
|
|
{
|
|
if(oobcounts!=0)
|
|
for(k=0; k<nclasses ; k++)
|
|
permimpbuf.m_yv.Mul(j*nclasses+k,1.0/(double)oobcounts);
|
|
v=0;
|
|
for(k=0; k<=nclasses-1; k++)
|
|
v=v+CMath::Sqr(permimpbuf.m_yv[j*nclasses+k]-permimpbuf.m_targety[k]);
|
|
permimpbuf.m_losses.Add(j,v);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sets report fields to their default values |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::CleanReport(CDecisionForestBuilder &s,CDFReport &rep)
|
|
{
|
|
rep.m_RelCLSError=0;
|
|
rep.m_AvgCE=0;
|
|
rep.m_RMSError=0;
|
|
rep.m_AvgError=0;
|
|
rep.m_AvgRelError=0;
|
|
rep.m_oobrelclserror=0;
|
|
rep.m_oobavgce=0;
|
|
rep.m_oobrmserror=0;
|
|
rep.m_oobavgerror=0;
|
|
rep.m_oobavgrelerror=0;
|
|
rep.m_topvars.Resize(s.m_NVars);
|
|
rep.m_varimportances=vector<double>::Zeros(s.m_NVars);
|
|
for(int i=0; i<=s.m_NVars-1; i++)
|
|
rep.m_topvars.Set(i,i);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns NRMS2 loss(sum of squared residuals) for a |
|
|
//| constant - output model: |
|
|
//| * model output is a mean over TRN set being passed (for |
|
|
//| classification problems - NClasses - dimensional vector |
|
|
//| of class probabilities) |
|
|
//| * model is evaluated over TST set being passed, with L2 loss |
|
|
//| being returned |
|
|
//| Input parameters: |
|
|
//| NClasses - ">1" for classification, "=1" for regression |
|
|
//| TrnLabelsI - training set labels, class indexes (for |
|
|
//| NClasses > 1) |
|
|
//| TrnLabelsR - training set output values (for NClasses = 1) |
|
|
//| TrnIdx0, |
|
|
//| TrnIdx1 - a range [Idx0, Idx1) of elements in LabelsI/R |
|
|
//| is considered |
|
|
//| TstLabelsI - training set labels, class indexes (for |
|
|
//| NClasses > 1) |
|
|
//| TstLabelsR - training set output values (for NClasses = 1) |
|
|
//| TstIdx0, |
|
|
//| TstIdx1 - a range [Idx0, Idx1) of elements in LabelsI/R |
|
|
//| is considered |
|
|
//| TmpI - temporary array, reallocated as needed |
|
|
//| Result: sum of squared residuals; for NClasses >= 2 it coincides |
|
|
//| with Gini impurity times(Idx1 - Idx0) |
|
|
//| Following fields of WorkBuf are used as temporaries: |
|
|
//| * TmpMeanNRMS2 |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::MeanNRMS2(int nclasses,CRowInt &trnlabelsi,
|
|
CRowDouble &trnlabelsr,int trnidx0,
|
|
int trnidx1,CRowInt &tstlabelsi,
|
|
CRowDouble &tstlabelsr,int tstidx0,
|
|
int tstidx1,CRowInt &tmpi)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int i=0;
|
|
int k=0;
|
|
int ntrn=trnidx1-trnidx0;
|
|
int ntst=tstidx1-tstidx0;
|
|
double v=0;
|
|
double vv=0;
|
|
double invntrn=0;
|
|
double pitrn=0;
|
|
double nitst=0;
|
|
//--- check
|
|
if(!CAp::Assert(trnidx0<=trnidx1,__FUNCTION__": integrity check failed (8754)"))
|
|
return(result);
|
|
if(!CAp::Assert(tstidx0<=tstidx1,__FUNCTION__": integrity check failed (8754)"))
|
|
return(result);
|
|
//--- Quick exit
|
|
if(ntrn==0 || ntst==0)
|
|
return(result);
|
|
invntrn=1.0/(double)ntrn;
|
|
if(nclasses>1)
|
|
{
|
|
//--- Classification problem
|
|
tmpi.Resize(2*nclasses);
|
|
tmpi.Fill(0);
|
|
for(i=trnidx0; i<trnidx1; i++)
|
|
tmpi.Add(trnlabelsi[i],1);
|
|
for(i=tstidx0; i<tstidx1; i++)
|
|
tmpi.Add(tstlabelsi[i]+nclasses,1);
|
|
for(i=0; i<nclasses; i++)
|
|
{
|
|
pitrn=tmpi[i]*invntrn;
|
|
nitst=tmpi[i+nclasses];
|
|
result+=nitst+pitrn*(ntst*pitrn-2*nitst);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- regression-specific code
|
|
v=0;
|
|
for(i=trnidx0; i<trnidx1; i++)
|
|
v+=trnlabelsr[i];
|
|
v=v*invntrn;
|
|
for(i=tstidx0; i<tstidx1 ; i++)
|
|
{
|
|
vv=tstlabelsr[i]-v;
|
|
result+=vv*vv;
|
|
}
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function is a part of the recurrent tree construction |
|
|
//| function; it selects variable for splitting according to current |
|
|
//| tree construction algorithm. |
|
|
//| Note: modifies VarsInPool, may decrease it if some variables |
|
|
//| become non-informative and leave the pool. |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::ChooseCurrentSplitDense(CDecisionForestBuilder &s,
|
|
CDFWorkBuf &workbuf,
|
|
int &varsinpool,
|
|
int varstoselect,
|
|
CHighQualityRandState &rs,
|
|
int idx0,
|
|
int idx1,
|
|
int &varbest,
|
|
double &splitbest)
|
|
{
|
|
//--- create variables
|
|
int npoints=0;
|
|
double errbest=0;
|
|
int varstried=0;
|
|
int varcur=0;
|
|
bool valuesaresame;
|
|
int offs=0;
|
|
double split=0;
|
|
int i=0;
|
|
double v=0;
|
|
double v0=0;
|
|
double currms=0;
|
|
int info=0;
|
|
|
|
varbest=0;
|
|
splitbest=0;
|
|
//---check
|
|
if(!CAp::Assert(s.m_DSType==0,__FUNCTION__": sparsity is not supported 4terg!"))
|
|
return;
|
|
if(!CAp::Assert(s.m_RDFAlgo==0,__FUNCTION__": integrity check failed (1657)"))
|
|
return;
|
|
if(!CAp::Assert(idx0<idx1,__FUNCTION__": integrity check failed (3445)"))
|
|
return;
|
|
npoints=s.m_NPoints;
|
|
//--- Select split according to dense direct RDF algorithm
|
|
varbest=-1;
|
|
errbest=CMath::m_maxrealnumber;
|
|
splitbest=0;
|
|
varstried=0;
|
|
while(varstried<MathMin(varstoselect,varsinpool))
|
|
{
|
|
//--- select variables from pool
|
|
workbuf.m_varpool.Swap(varstried,varstried+CHighQualityRand::HQRndUniformI(rs,varsinpool-varstried));
|
|
varcur=workbuf.m_varpool[varstried];
|
|
//--- Load variable values to working array.
|
|
//--- If all variable values are same, variable is excluded from pool and we re-run variable selection.
|
|
valuesaresame=true;
|
|
if(!CAp::Assert(s.m_DSType==0,"not supported segsv34fs"))
|
|
return;
|
|
offs=npoints*varcur;
|
|
v0=s.m_DSData[offs+workbuf.m_trnset[idx0]];
|
|
for(i=idx0; i<idx1; i++)
|
|
{
|
|
v=s.m_DSData[offs+workbuf.m_trnset[i]];
|
|
workbuf.m_curvals.Set(i,v);
|
|
valuesaresame=valuesaresame && v==v0;
|
|
}
|
|
if(valuesaresame)
|
|
{
|
|
//--- Variable does not change across current subset.
|
|
//--- Exclude variable from pool, go to the next iteration.
|
|
//--- VarsTried is not increased.
|
|
//--- NOTE: it is essential that updated VarsInPool is passed
|
|
//--- down to children but not up to caller - it is
|
|
//--- possible that one level higher this variable is
|
|
//--- not-fixed.
|
|
workbuf.m_varpool.Swap(varstried,varsinpool-1);
|
|
varsinpool=varsinpool-1;
|
|
continue;
|
|
}
|
|
//--- Now we are ready to infer the split
|
|
EvaluateDenseSplit(s,workbuf,rs,varcur,idx0,idx1,info,split,currms);
|
|
if(info>0 && (varbest<0 || currms<=errbest))
|
|
{
|
|
errbest=currms;
|
|
varbest=varcur;
|
|
splitbest=split;
|
|
for(i=idx0; i<idx1; i++)
|
|
workbuf.m_bestvals.Set(i,workbuf.m_curvals[i]);
|
|
}
|
|
//--- Next iteration
|
|
varstried=varstried+1;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs split on some specific dense variable |
|
|
//| whose values are stored in WorkBuf.CurVals[Idx0, Idx1) and labels|
|
|
//| are stored in WorkBuf.TrnLabelsR / I[Idx0, Idx1). |
|
|
//| It returns split value and associated RMS error. It is |
|
|
//| responsibility of the caller to make sure that variable has at |
|
|
//| least two distinct values, i.e. it is possible to make a split. |
|
|
//| Precomputed values of following fields of WorkBuf are used: |
|
|
//| * ClassPriors |
|
|
//| Following fields of WorkBuf are used as temporaries: |
|
|
//| * ClassTotals0, 1, 01 |
|
|
//| * Tmp0I, Tmp1I, Tmp0R, Tmp1R, Tmp2R, Tmp3R |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::EvaluateDenseSplit(CDecisionForestBuilder &s,
|
|
CDFWorkBuf &workbuf,
|
|
CHighQualityRandState &rs,
|
|
int splitvar,
|
|
int idx0,
|
|
int idx1,
|
|
int &info,
|
|
double &split,
|
|
double &rms)
|
|
{
|
|
//--- create variables
|
|
int nclasses=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double v2=0;
|
|
int sl=0;
|
|
int sr=0;
|
|
|
|
info=0;
|
|
split=0;
|
|
rms=0;
|
|
if(!CAp::Assert(idx0<idx1,__FUNCTION__": integrity check failed (8754)"))
|
|
return;
|
|
|
|
nclasses=s.m_NClasses;
|
|
if(s.m_DSBinary[splitvar])
|
|
{
|
|
//--- Try simple binary split, if possible
|
|
//--- Split can be inferred from minimum/maximum values, just calculate RMS error
|
|
info=1;
|
|
split=GetSplit(s,s.m_DSMin[splitvar],s.m_DSMax[splitvar],rs);
|
|
if(nclasses>1)
|
|
{
|
|
//--- Classification problem
|
|
workbuf.m_classtotals0.Fill(0);
|
|
sl=0;
|
|
for(i=idx0; i<idx1; i++)
|
|
{
|
|
if(workbuf.m_curvals[i]<split)
|
|
{
|
|
j=workbuf.m_trnlabelsi[i];
|
|
workbuf.m_classtotals0.Add(j,1);
|
|
sl++;
|
|
}
|
|
}
|
|
sr=idx1-idx0-sl;
|
|
if(!CAp::Assert(sl!=0 && sr!=0,__FUNCTION__": something strange,impossible failure!"))
|
|
return;
|
|
v0=1.0/(double)sl;
|
|
v1=1.0/(double)sr;
|
|
rms=0;
|
|
for(j=0; j<nclasses; j++)
|
|
{
|
|
k0=workbuf.m_classtotals0[j];
|
|
k1=workbuf.m_classpriors[j]-k0;
|
|
rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
|
|
}
|
|
rms=MathSqrt(rms/(nclasses*(idx1-idx0+1)));
|
|
}
|
|
else
|
|
{
|
|
//--- regression-specific code
|
|
sl=0;
|
|
sr=0;
|
|
v1=0;
|
|
v2=0;
|
|
for(j=idx0; j<idx1; j++)
|
|
{
|
|
if(workbuf.m_curvals[j]<split)
|
|
{
|
|
v1+=workbuf.m_trnlabelsr[j];
|
|
sl++;
|
|
}
|
|
else
|
|
{
|
|
v2+=workbuf.m_trnlabelsr[j];
|
|
sr++;
|
|
}
|
|
}
|
|
if(!CAp::Assert(sl!=0 && sr!=0,__FUNCTION__": something strange,impossible failure!"))
|
|
return;
|
|
v1=v1/sl;
|
|
v2=v2/sr;
|
|
rms=0;
|
|
for(j=0; j<idx1-idx0; j++)
|
|
{
|
|
v=workbuf.m_trnlabelsr[idx0+j];
|
|
if(workbuf.m_curvals[j]<split)
|
|
v-=v1;
|
|
else
|
|
v-=v2;
|
|
rms+=v*v;
|
|
}
|
|
rms=MathSqrt(rms/(idx1-idx0+1));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- General split
|
|
info=0;
|
|
if(nclasses>1)
|
|
{
|
|
for(i=0; i<idx1-idx0; i++)
|
|
{
|
|
workbuf.m_tmp0r.Set(i,workbuf.m_curvals[idx0+i]);
|
|
workbuf.m_tmp0i.Set(i,workbuf.m_trnlabelsi[idx0+i]);
|
|
}
|
|
ClassifierSplit(s,workbuf,workbuf.m_tmp0r,workbuf.m_tmp0i,idx1-idx0,rs,info,split,rms,workbuf.m_tmp1r,workbuf.m_tmp1i);
|
|
}
|
|
else
|
|
{
|
|
for(i=0; i<idx1-idx0; i++)
|
|
{
|
|
workbuf.m_tmp0r.Set(i,workbuf.m_curvals[idx0+i]);
|
|
workbuf.m_tmp1r.Set(i,workbuf.m_trnlabelsr[idx0+i]);
|
|
}
|
|
RegressionSplit(s,workbuf,workbuf.m_tmp0r,workbuf.m_tmp1r,idx1-idx0,info,split,rms,workbuf.m_tmp2r,workbuf.m_tmp3r);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Classifier split |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::ClassifierSplit(CDecisionForestBuilder &s,
|
|
CDFWorkBuf &workbuf,
|
|
CRowDouble &x,
|
|
CRowInt &c,
|
|
int n,
|
|
CHighQualityRandState &rs,
|
|
int &info,
|
|
double &threshold,
|
|
double &e,
|
|
CRowDouble &sortrbuf,
|
|
CRowInt &sortibuf)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int n0=0;
|
|
int n0prev=0;
|
|
double v=0;
|
|
int advanceby=0;
|
|
double rms=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
int nclasses=0;
|
|
double vmin=0;
|
|
double vmax=0;
|
|
|
|
info=0;
|
|
threshold=0;
|
|
e=0;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_RDFSplitStrength==0 || s.m_RDFSplitStrength==1 || s.m_RDFSplitStrength==2,__FUNCTION__": unexpected split type at ClassifierSplit()"))
|
|
return;
|
|
|
|
nclasses=s.m_NClasses;
|
|
advanceby=1;
|
|
if(n>=20)
|
|
advanceby=MathMax(2,(int)MathRound(n*0.05));
|
|
info=-1;
|
|
threshold=0;
|
|
e=CMath::m_maxrealnumber;
|
|
//--- Random split
|
|
if(s.m_RDFSplitStrength==0)
|
|
{
|
|
//--- Evaluate minimum, maximum and randomly selected values
|
|
vmin=x[0];
|
|
vmax=x[0];
|
|
for(i=1; i<n; i++)
|
|
{
|
|
v=x[i];
|
|
if(v<vmin)
|
|
vmin=v;
|
|
if(v>vmax)
|
|
vmax=v;
|
|
}
|
|
if(vmin==vmax)
|
|
return;
|
|
v=x[CHighQualityRand::HQRndUniformI(rs,n)];
|
|
if(v==vmin)
|
|
v=vmax;
|
|
//--- Calculate RMS error associated with the split
|
|
workbuf.m_classtotals0.Fill(0,0,nclasses);
|
|
n0=0;
|
|
for(i=0; i<n; i++)
|
|
if(x[i]<v)
|
|
{
|
|
k=c[i];
|
|
workbuf.m_classtotals0.Add(k,1);
|
|
n0++;
|
|
}
|
|
if(!CAp::Assert(n0>0 && n0<n,__FUNCTION__": critical integrity check failed at ClassifierSplit()"))
|
|
return;
|
|
v0=1.0/(double)n0;
|
|
v1=1.0/(double)(n-n0);
|
|
rms=0;
|
|
for(j=0; j<nclasses; j++)
|
|
{
|
|
k0=workbuf.m_classtotals0[j];
|
|
k1=workbuf.m_classpriors[j]-k0;
|
|
rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
|
|
}
|
|
threshold=v;
|
|
info=1;
|
|
e=rms;
|
|
return;
|
|
}
|
|
//--- Stronger splits which require us to sort the data
|
|
//--- Quick check for degeneracy
|
|
CTSort::TagSortFastI(x,c,sortrbuf,sortibuf,n);
|
|
v=0.5*(x[0]+x[n-1]);
|
|
if(!(x[0]<v && v<x[n-1]))
|
|
return;
|
|
switch(s.m_RDFSplitStrength)
|
|
{
|
|
//--- Split at the middle
|
|
case 1:
|
|
//--- Select split position
|
|
vmin=x[0];
|
|
vmax=x[n-1];
|
|
v=x[n/2];
|
|
if(v==vmin)
|
|
v=vmin+0.001*(vmax-vmin);
|
|
if(v==vmin)
|
|
v=vmax;
|
|
//--- Calculate RMS error associated with the split
|
|
workbuf.m_classtotals0.Fill(0,0,nclasses);
|
|
n0=0;
|
|
for(i=0; i<n; i++)
|
|
if(x[i]<v)
|
|
{
|
|
k=c[i];
|
|
workbuf.m_classtotals0.Add(k,1);
|
|
n0++;
|
|
}
|
|
if(!CAp::Assert(n0>0 && n0<n,__FUNCTION__": critical integrity check failed at ClassifierSplit()"))
|
|
return;
|
|
v0=1.0/(double)n0;
|
|
v1=1.0/(double)(n-n0);
|
|
rms=0;
|
|
for(j=0; j<nclasses; j++)
|
|
{
|
|
k0=workbuf.m_classtotals0[j];
|
|
k1=workbuf.m_classpriors[j]-k0;
|
|
rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
|
|
}
|
|
threshold=v;
|
|
info=1;
|
|
e=rms;
|
|
break;
|
|
//--- Strong split
|
|
case 2:
|
|
//--- Prepare initial split.
|
|
//--- Evaluate current split, prepare next one, repeat.
|
|
workbuf.m_classtotals0.Fill(0,0,nclasses);
|
|
n0=1;
|
|
while(n0<n && x[n0]==x[n0-1])
|
|
n0++;
|
|
if(!CAp::Assert(n0<n,__FUNCTION__": critical integrity check failed in ClassifierSplit()"))
|
|
return;
|
|
for(i=0; i<n0; i++)
|
|
{
|
|
k=c[i];
|
|
workbuf.m_classtotals0.Add(k,1);
|
|
}
|
|
info=-1;
|
|
threshold=x[n-1];
|
|
e=CMath::m_maxrealnumber;
|
|
while(n0<n)
|
|
{
|
|
//--- RMS error associated with current split
|
|
v0=1.0/(double)n0;
|
|
v1=1.0/(double)(n-n0);
|
|
rms=0;
|
|
for(j=0; j<nclasses; j++)
|
|
{
|
|
k0=workbuf.m_classtotals0[j];
|
|
k1=workbuf.m_classpriors[j]-k0;
|
|
rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
|
|
}
|
|
if(info<0 || rms<e)
|
|
{
|
|
info=1;
|
|
e=rms;
|
|
threshold=0.5*(x[n0-1]+x[n0]);
|
|
if(threshold<=x[n0-1])
|
|
threshold=x[n0];
|
|
}
|
|
//--- Advance
|
|
n0prev=n0;
|
|
while(n0<n && (n0-n0prev)<advanceby)
|
|
{
|
|
v=x[n0];
|
|
while(n0<n && x[n0]==v)
|
|
{
|
|
k=c[n0];
|
|
workbuf.m_classtotals0.Add(k,1);
|
|
n0++;
|
|
}
|
|
}
|
|
}
|
|
if(info>0)
|
|
e=MathSqrt(e/(nclasses*n));
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__": ClassifierSplit(),critical error");
|
|
break;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Regression model split |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::RegressionSplit(CDecisionForestBuilder &s,
|
|
CDFWorkBuf &workbuf,
|
|
CRowDouble &x,
|
|
CRowDouble &y,
|
|
int n,
|
|
int &info,
|
|
double &threshold,
|
|
double &e,
|
|
CRowDouble &sortrbuf,
|
|
CRowDouble &sortrbuf2)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
double vmin=0;
|
|
double vmax=0;
|
|
double bnd01=0;
|
|
double bnd12=0;
|
|
double bnd23=0;
|
|
int total0=0;
|
|
int total1=0;
|
|
int total2=0;
|
|
int total3=0;
|
|
int cnt0=0;
|
|
int cnt1=0;
|
|
int cnt2=0;
|
|
int cnt3=0;
|
|
int n0=0;
|
|
int advanceby=0;
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double rms=0;
|
|
int n0prev=0;
|
|
int k0=0;
|
|
int k1=0;
|
|
|
|
info=0;
|
|
threshold=0;
|
|
e=0;
|
|
advanceby=1;
|
|
if(n>=20)
|
|
advanceby=MathMax(2,(int)MathRound(n*0.05));
|
|
//--- Sort data
|
|
//--- Quick check for degeneracy
|
|
CTSort::TagSortFastR(x,y,sortrbuf,sortrbuf2,n);
|
|
v=0.5*(x[0]+x[n-1]);
|
|
if(!(x[0]<v && v<x[n-1]))
|
|
{
|
|
info=-1;
|
|
threshold=x[n-1];
|
|
e=CMath::m_maxrealnumber;
|
|
return;
|
|
}
|
|
//--- Prepare initial split.
|
|
//--- Evaluate current split, prepare next one, repeat.
|
|
vmin=y[0];
|
|
vmax=y[0];
|
|
for(i=1; i<n; i++)
|
|
{
|
|
v=y[i];
|
|
if(v<vmin)
|
|
vmin=v;
|
|
if(v>vmax)
|
|
vmax=v;
|
|
}
|
|
bnd12=0.5*(vmin+vmax);
|
|
bnd01=0.5*(vmin+bnd12);
|
|
bnd23=0.5*(vmax+bnd12);
|
|
total0=0;
|
|
total1=0;
|
|
total2=0;
|
|
total3=0;
|
|
for(i=0; i<n; i++)
|
|
{
|
|
v=y[i];
|
|
if(v<bnd12)
|
|
{
|
|
if(v<bnd01)
|
|
total0++;
|
|
else
|
|
total1++;
|
|
}
|
|
else
|
|
{
|
|
if(v<bnd23)
|
|
total2++;
|
|
else
|
|
total3++;
|
|
}
|
|
}
|
|
n0=1;
|
|
while(n0<n && x[n0]==x[n0-1])
|
|
n0++;
|
|
if(!CAp::Assert(n0<n,__FUNCTION__": critical integrity check failed in ClassifierSplit()"))
|
|
return;
|
|
|
|
cnt0=0;
|
|
cnt1=0;
|
|
cnt2=0;
|
|
cnt3=0;
|
|
for(i=0; i<n0; i++)
|
|
{
|
|
v=y[i];
|
|
if(v<bnd12)
|
|
{
|
|
if(v<bnd01)
|
|
cnt0++;
|
|
else
|
|
cnt1++;
|
|
}
|
|
else
|
|
{
|
|
if(v<bnd23)
|
|
cnt2++;
|
|
else
|
|
cnt3++;
|
|
}
|
|
}
|
|
|
|
info=-1;
|
|
threshold=x[n-1];
|
|
e=CMath::m_maxrealnumber;
|
|
while(n0<n)
|
|
{
|
|
//--- RMS error associated with current split
|
|
v0=1.0/(double)n0;
|
|
v1=1.0/(double)(n-n0);
|
|
rms=0;
|
|
k0=cnt0;
|
|
k1=total0-cnt0;
|
|
rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
|
|
k0=cnt1;
|
|
k1=total1-cnt1;
|
|
rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
|
|
k0=cnt2;
|
|
k1=total2-cnt2;
|
|
rms+=k0*(1-v0*k0)+k1*(1-v1*k1);
|
|
k0=cnt3;
|
|
k1=total3-cnt3;
|
|
rms=rms+k0*(1-v0*k0)+k1*(1-v1*k1);
|
|
if(info<0 || rms<e)
|
|
{
|
|
info=1;
|
|
e=rms;
|
|
threshold=0.5*(x[n0-1]+x[n0]);
|
|
if(threshold<=x[n0-1])
|
|
threshold=x[n0];
|
|
}
|
|
//--- Advance
|
|
n0prev=n0;
|
|
while(n0<n && (n0-n0prev)<advanceby)
|
|
{
|
|
v0=x[n0];
|
|
while(n0<n && x[n0]==v0)
|
|
{
|
|
v=y[n0];
|
|
if(v<bnd12)
|
|
{
|
|
if(v<bnd01)
|
|
cnt0++;
|
|
else
|
|
cnt1++;
|
|
}
|
|
else
|
|
{
|
|
if(v<bnd23)
|
|
cnt2++;
|
|
else
|
|
cnt3++;
|
|
}
|
|
n0++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(info>0)
|
|
e=MathSqrt(e/(4*n));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns split: either deterministic split at the middle |
|
|
//| of [A, B], or randomly chosen split. |
|
|
//| It is guaranteed that A < Split <= B. |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::GetSplit(CDecisionForestBuilder &s,double a,
|
|
double b,CHighQualityRandState &rs)
|
|
{
|
|
double result=0.5*(a+b);
|
|
//--- check
|
|
if(result<=a)
|
|
result=b;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Outputs leaf to the tree |
|
|
//| Following items of TRN and OOB sets are updated in the voting |
|
|
//| buffer: |
|
|
//| * items [Idx0, Idx1) of WorkBuf.TrnSet |
|
|
//| * items [OOBIdx0, OOBIdx1) of WorkBuf.OOBSet |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::OutputLeaf(CDecisionForestBuilder &s,
|
|
CDFWorkBuf &workbuf,
|
|
CRowDouble &treebuf,
|
|
CDFVoteBuf &votebuf,
|
|
int idx0,
|
|
int idx1,
|
|
int oobidx0,
|
|
int oobidx1,
|
|
int &treesize,
|
|
double leafval)
|
|
{
|
|
//--- create variables
|
|
int nclasses=s.m_NClasses;
|
|
int leafvali=0;
|
|
int i=0;
|
|
int j=0;
|
|
|
|
if(nclasses==1)
|
|
{
|
|
//--- Store split to the tree
|
|
treebuf.Set(treesize,-1);
|
|
treebuf.Set(treesize+1,leafval);
|
|
//--- Update training and OOB voting stats
|
|
for(i=idx0; i<idx1; i++)
|
|
{
|
|
j=workbuf.m_trnset[i];
|
|
votebuf.m_trntotals.Add(j,leafval);
|
|
votebuf.m_trncounts.Add(j,1);
|
|
}
|
|
for(i=oobidx0; i<oobidx1; i++)
|
|
{
|
|
j=workbuf.m_oobset[i];
|
|
votebuf.m_oobtotals.Add(j,leafval);
|
|
votebuf.m_oobcounts.Add(j,1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Store split to the tree
|
|
treebuf.Set(treesize,-1);
|
|
treebuf.Set(treesize+1,leafval);
|
|
//--- Update training and OOB voting stats
|
|
leafvali=(int)MathRound(leafval);
|
|
for(i=idx0; i<idx1; i++)
|
|
{
|
|
j=workbuf.m_trnset[i];
|
|
votebuf.m_trntotals.Add(j*nclasses+leafvali,1);
|
|
votebuf.m_trncounts.Add(j,1);
|
|
}
|
|
for(i=oobidx0; i<=oobidx1-1; i++)
|
|
{
|
|
j=workbuf.m_oobset[i];
|
|
votebuf.m_oobtotals.Add(j*nclasses+leafvali,1);
|
|
votebuf.m_oobcounts.Add(j,1);
|
|
}
|
|
}
|
|
treesize+=m_LeafNodeWdth;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs generic and algorithm - specific |
|
|
//| preprocessing of the dataset |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::AnalyzeAndPreprocessDataset(CDecisionForestBuilder &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_DSType==0,__FUNCTION__": no sparsity"))
|
|
return;
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
bool isbinary;
|
|
double v=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
CHighQualityRandState rs;
|
|
int npoints=s.m_NPoints;
|
|
int nvars=s.m_NVars;
|
|
int nclasses=s.m_NClasses;
|
|
//--- seed local RNG
|
|
if(s.m_RDFGlobalSeed>0)
|
|
CHighQualityRand::HQRndSeed(s.m_RDFGlobalSeed,3532,rs);
|
|
else
|
|
CHighQualityRand::HQRndSeed(CMath::RandomInteger(30000),3532,rs);
|
|
//--- Generic processing
|
|
if(!CAp::Assert(npoints>=1,__FUNCTION__": integrity check failed"))
|
|
return;
|
|
s.m_DSMin.Resize(nvars);
|
|
s.m_DSMax.Resize(nvars);
|
|
CApServ::BVectorSetLengthAtLeast(s.m_DSBinary,nvars);
|
|
for(i=0; i<nvars; i++)
|
|
{
|
|
v0=s.m_DSData[i*npoints];
|
|
v1=s.m_DSData[i*npoints];
|
|
for(j=1; j<npoints; j++)
|
|
{
|
|
v=s.m_DSData[i*npoints+j];
|
|
if(v<v0)
|
|
v0=v;
|
|
if(v>v1)
|
|
v1=v;
|
|
}
|
|
s.m_DSMin.Set(i,v0);
|
|
s.m_DSMax.Set(i,v1);
|
|
if(!CAp::Assert(v0<=v1,__FUNCTION__": strange integrity check failure"))
|
|
return;
|
|
isbinary=true;
|
|
for(j=0; j<npoints; j++)
|
|
{
|
|
v=s.m_DSData[i*npoints+j];
|
|
isbinary=isbinary && (v==v0 || v==v1);
|
|
}
|
|
s.m_DSBinary[i]=isbinary;
|
|
}
|
|
if(nclasses==1)
|
|
{
|
|
s.m_DSRAvg=0;
|
|
for(i=0; i<npoints; i++)
|
|
s.m_DSRAvg+=s.m_DSRVal[i];
|
|
s.m_DSRAvg=s.m_DSRAvg/npoints;
|
|
}
|
|
else
|
|
{
|
|
s.m_DSCTotals.Resize(nclasses);
|
|
s.m_DSCTotals.Fill(0);
|
|
for(i=0; i<npoints; i++)
|
|
s.m_DSCTotals.Add(s.m_DSIVal[i],1);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function merges together trees generated during training and|
|
|
//| outputs it to the decision forest. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| NTrees - NTrees >= 1, number of trees to train |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| DF - decision forest |
|
|
//| Rep - report |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::MergeTrees(CDecisionForestBuilder &s,
|
|
CDecisionForest &df)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int cursize=0;
|
|
int offs=0;
|
|
CRowInt treesizes;
|
|
CRowInt treeoffsets;
|
|
//--- initialize
|
|
df.m_ForestFormat=m_DFUncompressedV0;
|
|
df.m_NVars=s.m_NVars;
|
|
df.m_NClasses=s.m_NClasses;
|
|
df.m_BufSize=0;
|
|
//--- Determine trees count
|
|
df.m_NTrees=ArraySize(s.m_TreeBuf);
|
|
if(!CAp::Assert(df.m_NTrees>0,__FUNCTION__": integrity check failed,zero trees count"))
|
|
return;
|
|
//--- Determine individual tree sizes and total buffer size
|
|
treesizes.Resize(df.m_NTrees);
|
|
treesizes.Fill(-1);
|
|
for(int idx=0; idx<df.m_NTrees; idx++)
|
|
{
|
|
if(!CAp::Assert(s.m_TreeBuf[idx].m_treeidx>=0 && s.m_TreeBuf[idx].m_treeidx<df.m_NTrees,__FUNCTION__": integrity check failed (wrong TreeIdx)"))
|
|
return;
|
|
if(!CAp::Assert(treesizes[s.m_TreeBuf[idx].m_treeidx]<0,__FUNCTION__": integrity check failed (duplicate TreeIdx)"))
|
|
return;
|
|
int size=(int)MathRound(s.m_TreeBuf[idx].m_treebuf[0]);
|
|
if(!CAp::Assert(size>0,__FUNCTION__": integrity check failed (wrong TreeSize)"))
|
|
return;
|
|
df.m_BufSize+=size;
|
|
treesizes.Set(s.m_TreeBuf[idx].m_treeidx,size);
|
|
}
|
|
//--- Determine offsets for individual trees in output buffer
|
|
treeoffsets.Resize(df.m_NTrees);
|
|
treeoffsets.Set(0,0);
|
|
for(i=1; i<df.m_NTrees; i++)
|
|
treeoffsets.Set(i,treeoffsets[i-1]+treesizes[i-1]);
|
|
//--- Output trees
|
|
//--- NOTE: since ALGLIB 3.16.0 trees are sorted by tree index prior to
|
|
//--- output (necessary for variable importance estimation), that's
|
|
//--- why we need array of tree offsets
|
|
df.m_Trees.Resize(df.m_BufSize);
|
|
for(int idx=0; idx<df.m_NTrees; idx++)
|
|
{
|
|
cursize=(int)MathRound(s.m_TreeBuf[idx].m_treebuf[0]);
|
|
offs=treeoffsets[s.m_TreeBuf[idx].m_treeidx];
|
|
for(i=0; i<cursize; i++)
|
|
df.m_Trees.Set(offs+i,s.m_TreeBuf[idx].m_treebuf[i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function post - processes voting array and calculates TRN |
|
|
//| and OOB errors. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| NTrees - number of trees in the forest |
|
|
//| Buf - possibly preallocated vote buffer, its contents is |
|
|
//| overwritten by this function |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Rep - report fields corresponding to errors are updated |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::ProcessVotingResults(CDecisionForestBuilder &s,
|
|
int ntrees,
|
|
CDFVoteBuf &buf,
|
|
CDFReport &rep)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int k1=0;
|
|
double v=0;
|
|
int avgrelcnt=0;
|
|
int oobavgrelcnt=0;
|
|
int npoints=s.m_NPoints;
|
|
int nvars=s.m_NVars;
|
|
int nclasses=s.m_NClasses;
|
|
//--- check
|
|
if(!CAp::Assert(npoints>0,__FUNCTION__": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(nvars>0,__FUNCTION__": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(nclasses>0,__FUNCTION__": integrity check failed"))
|
|
return;
|
|
//--- Prepare vote buffer
|
|
buf.m_trntotals=vector<double>::Zeros(npoints*nclasses);
|
|
buf.m_oobtotals=vector<double>::Zeros(npoints*nclasses);
|
|
buf.m_trncounts.Resize(npoints);
|
|
buf.m_oobcounts.Resize(npoints);
|
|
buf.m_trncounts.Fill(0);
|
|
buf.m_oobcounts.Fill(0);
|
|
//--- Merge voting arrays
|
|
for(int idx=0; idx<ntrees; idx++)
|
|
{
|
|
buf.m_trntotals+=s.m_VoteBuf[idx].m_trntotals+s.m_VoteBuf[idx].m_oobtotals+0;
|
|
buf.m_oobtotals+=s.m_VoteBuf[idx].m_oobtotals;
|
|
for(i=0; i<=npoints-1; i++)
|
|
{
|
|
buf.m_trncounts.Add(i,s.m_VoteBuf[idx].m_trncounts[i]+s.m_VoteBuf[idx].m_oobcounts[i]);
|
|
buf.m_oobcounts.Add(i,s.m_VoteBuf[idx].m_oobcounts[i]);
|
|
}
|
|
}
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
v=1.0/CApServ::Coalesce(buf.m_trncounts[i],1);
|
|
for(j=0; j<nclasses; j++)
|
|
buf.m_trntotals.Mul(i*nclasses+j,v);
|
|
v=1.0/CApServ::Coalesce(buf.m_oobcounts[i],1);
|
|
for(j=0; j<=nclasses-1; j++)
|
|
buf.m_oobtotals.Mul(i*nclasses+j,v);
|
|
}
|
|
//--- Use aggregated voting data to output error metrics
|
|
avgrelcnt=0;
|
|
oobavgrelcnt=0;
|
|
rep.m_RMSError=0;
|
|
rep.m_AvgError=0;
|
|
rep.m_AvgRelError=0;
|
|
rep.m_RelCLSError=0;
|
|
rep.m_AvgCE=0;
|
|
rep.m_oobrmserror=0;
|
|
rep.m_oobavgerror=0;
|
|
rep.m_oobavgrelerror=0;
|
|
rep.m_oobrelclserror=0;
|
|
rep.m_oobavgce=0;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
if(nclasses>1)
|
|
{
|
|
//--- classification-specific code
|
|
k=s.m_DSIVal[i];
|
|
for(j=0; j<nclasses; j++)
|
|
{
|
|
v=buf.m_trntotals[i*nclasses+j];
|
|
if(j==k)
|
|
{
|
|
rep.m_AvgCE-=MathLog(CApServ::Coalesce(v,CMath::m_minrealnumber));
|
|
rep.m_RMSError+=CMath::Sqr(v-1);
|
|
rep.m_AvgError+=MathAbs(v-1);
|
|
rep.m_AvgRelError+=MathAbs(v-1);
|
|
avgrelcnt++;
|
|
}
|
|
else
|
|
{
|
|
rep.m_RMSError+=CMath::Sqr(v);
|
|
rep.m_AvgError+=MathAbs(v);
|
|
}
|
|
v=buf.m_oobtotals[i*nclasses+j];
|
|
if(j==k)
|
|
{
|
|
rep.m_oobavgce-=MathLog(CApServ::Coalesce(v,CMath::m_minrealnumber));
|
|
rep.m_oobrmserror+=CMath::Sqr(v-1);
|
|
rep.m_oobavgerror+=MathAbs(v-1);
|
|
rep.m_oobavgrelerror+=MathAbs(v-1);
|
|
oobavgrelcnt++;
|
|
}
|
|
else
|
|
{
|
|
rep.m_oobrmserror+=CMath::Sqr(v);
|
|
rep.m_oobavgerror+=MathAbs(v);
|
|
}
|
|
}
|
|
//--- Classification errors are handled separately
|
|
k1=0;
|
|
for(j=1; j<nclasses; j++)
|
|
{
|
|
if(buf.m_trntotals[i*nclasses+j]>buf.m_trntotals[i*nclasses+k1])
|
|
k1=j;
|
|
}
|
|
if(k1!=k)
|
|
rep.m_RelCLSError++;
|
|
k1=0;
|
|
for(j=1; j<nclasses; j++)
|
|
{
|
|
if(buf.m_oobtotals[i*nclasses+j]>buf.m_oobtotals[i*nclasses+k1])
|
|
k1=j;
|
|
}
|
|
if(k1!=k)
|
|
rep.m_oobrelclserror++;
|
|
}
|
|
else
|
|
{
|
|
//--- regression-specific code
|
|
v=buf.m_trntotals[i]-s.m_DSRVal[i];
|
|
rep.m_RMSError+=CMath::Sqr(v);
|
|
rep.m_AvgError+=MathAbs(v);
|
|
if(s.m_DSRVal[i]!=0.0)
|
|
{
|
|
rep.m_AvgRelError+=MathAbs(v/s.m_DSRVal[i]);
|
|
avgrelcnt++;
|
|
}
|
|
v=buf.m_oobtotals[i]-s.m_DSRVal[i];
|
|
rep.m_oobrmserror+=CMath::Sqr(v);
|
|
rep.m_oobavgerror+=MathAbs(v);
|
|
if(s.m_DSRVal[i]!=0.0)
|
|
{
|
|
rep.m_oobavgrelerror+=MathAbs(v/s.m_DSRVal[i]);
|
|
oobavgrelcnt++;
|
|
}
|
|
}
|
|
}
|
|
rep.m_RelCLSError/=npoints;
|
|
rep.m_RMSError=MathSqrt(rep.m_RMSError/(npoints*nclasses));
|
|
rep.m_AvgError/=(npoints*nclasses);
|
|
rep.m_AvgRelError/=CApServ::Coalesce(avgrelcnt,1);
|
|
rep.m_oobrelclserror/=npoints;
|
|
rep.m_oobrmserror=MathSqrt(rep.m_oobrmserror/(npoints*nclasses));
|
|
rep.m_oobavgerror/=(npoints*nclasses);
|
|
rep.m_oobavgrelerror/=CApServ::Coalesce(oobavgrelcnt,1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs binary compression of decision forest, |
|
|
//| using either 8-bit mantissa (a bit more compact representation) |
|
|
//| or 16-bit mantissa for splits and regression outputs. |
|
|
//| Forest is compressed in - place. |
|
|
//| Return value is a compression factor. |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::BinaryCompression(CDecisionForest &df,
|
|
bool usemantissa8)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int size8=0;
|
|
int size8i=0;
|
|
int offssrc=0;
|
|
int offsdst=0;
|
|
int maxrawtreesize=0;
|
|
CRowInt dummyi;
|
|
CRowInt compressedsizes;
|
|
//--- Quick exit if already compressed
|
|
if(df.m_ForestFormat==m_DFCompressedV0)
|
|
return(1.0);
|
|
//--- Check that source format is supported
|
|
if(!CAp::Assert(df.m_ForestFormat==m_DFUncompressedV0,__FUNCTION__": unexpected forest format"))
|
|
return(result);
|
|
//--- Compute sizes of uncompressed and compressed trees.
|
|
for(int i=0; i<df.m_NTrees; i++)
|
|
{
|
|
size8i=ComputeCompressedSizeRec(df,usemantissa8,offssrc,offssrc+1,dummyi,false);
|
|
size8+=ComputeCompressedUintSize(size8i)+size8i;
|
|
maxrawtreesize=MathMax(maxrawtreesize,(int)MathRound(df.m_Trees[offssrc]));
|
|
offssrc+=(int)MathRound(df.m_Trees[offssrc]);
|
|
}
|
|
result=(double)(8.0*df.m_Trees.Size())/(double)(size8+1.0);
|
|
//--- Allocate memory and perform compression
|
|
df.m_Trees8.Resize(size8);
|
|
compressedsizes.Resize(maxrawtreesize);
|
|
offssrc=0;
|
|
offsdst=0;
|
|
for(int i=0; i<df.m_NTrees; i++)
|
|
{
|
|
//--- Call compressed size evaluator one more time, now saving subtree sizes into temporary array
|
|
size8i=ComputeCompressedSizeRec(df,usemantissa8,offssrc,offssrc+1,compressedsizes,true);
|
|
//--- Output tree header (length in bytes)
|
|
StreamUint(df.m_Trees8,offsdst,size8i);
|
|
//--- Compress recursively
|
|
CompressRec(df,usemantissa8,offssrc,offssrc+1,compressedsizes,df.m_Trees8,offsdst);
|
|
//--- Next tree
|
|
offssrc+=(int)MathRound(df.m_Trees[offssrc]);
|
|
}
|
|
if(!CAp::Assert(offsdst==size8,__FUNCTION__": integrity check failed (stream length)"))
|
|
return(result);
|
|
//--- Finalize forest conversion, clear previously allocated memory
|
|
df.m_ForestFormat=m_DFCompressedV0;
|
|
df.m_UseMantissa8=usemantissa8;
|
|
df.m_Trees.Resize(0);
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns exact number of bytes required to store |
|
|
//| compressed version of the tree starting at location TreeBase. |
|
|
//| PARAMETERS: |
|
|
//| DF - decision forest |
|
|
//| UseMantissa8- whether 8-bit or 16-bit mantissas are used to |
|
|
//| store floating point numbers |
|
|
//| TreeRoot - root of the specific tree being stored(offset |
|
|
//| in DF.Trees) |
|
|
//| TreePos - position within tree(first location in the tree |
|
|
//| is TreeRoot + 1) |
|
|
//| CompressedSizes - not referenced if SaveCompressedSizes is |
|
|
//| False; otherwise, values computed by this |
|
|
//| function for specific values of TreePos are |
|
|
//| stored to CompressedSizes[TreePos - TreeRoot] |
|
|
//| (other elements of the array are not referenced)|
|
|
//| This array must be preallocated by caller. |
|
|
//+------------------------------------------------------------------+
|
|
int CDForest::ComputeCompressedSizeRec(CDecisionForest &df,
|
|
bool usemantissa8,
|
|
int treeroot,
|
|
int treepos,
|
|
CRowInt &compressedsizes,
|
|
bool savecompressedsizes)
|
|
{
|
|
//--- create variables
|
|
int result=0;
|
|
int jmponbranch=0;
|
|
int child0size=0;
|
|
int child1size=0;
|
|
int fpwidth=0;
|
|
//--- check
|
|
if(usemantissa8)
|
|
fpwidth=2;
|
|
else
|
|
fpwidth=3;
|
|
//--- Leaf or split?
|
|
if(df.m_Trees[treepos]==-1.0)
|
|
{
|
|
//--- Leaf
|
|
result=ComputeCompressedUintSize(2*df.m_NVars);
|
|
if(df.m_NClasses==1)
|
|
result+=fpwidth;
|
|
else
|
|
result+=ComputeCompressedUintSize((int)MathRound(df.m_Trees[treepos+1]));
|
|
}
|
|
else
|
|
{
|
|
//--- Split
|
|
jmponbranch=(int)MathRound(df.m_Trees[treepos+2]);
|
|
child0size=ComputeCompressedSizeRec(df,usemantissa8,treeroot,treepos+m_InnerNodeWidth,compressedsizes,savecompressedsizes);
|
|
child1size=ComputeCompressedSizeRec(df,usemantissa8,treeroot,treeroot+jmponbranch,compressedsizes,savecompressedsizes);
|
|
if(child0size<=child1size)
|
|
{
|
|
//--- Child #0 comes first because it is shorter
|
|
result=ComputeCompressedUintSize((int)MathRound(df.m_Trees[treepos]));
|
|
result+=fpwidth;
|
|
result+=ComputeCompressedUintSize(child0size);
|
|
}
|
|
else
|
|
{
|
|
//--- Child #1 comes first because it is shorter
|
|
result=ComputeCompressedUintSize((int)MathRound(df.m_Trees[treepos])+df.m_NVars);
|
|
result+=fpwidth;
|
|
result+=ComputeCompressedUintSize(child1size);
|
|
}
|
|
result+=child0size+child1size;
|
|
}
|
|
//--- Do we have to save compressed sizes?
|
|
if(savecompressedsizes)
|
|
{
|
|
if(!CAp::Assert(treepos-treeroot<compressedsizes.Size(),__FUNCTION__": integrity check failed"))
|
|
return(result);
|
|
compressedsizes.Set(treepos-treeroot,result);
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns exact number of bytes required to store |
|
|
//| compressed version of the tree starting at location TreeBase. |
|
|
//| PARAMETERS: |
|
|
//| DF - decision forest |
|
|
//| UseMantissa8 - whether 8-bit or 16-bit mantissas are used |
|
|
//| to store floating point numbers |
|
|
//| TreeRoot - root of the specific tree being stored(offset |
|
|
//| in DF.Trees) |
|
|
//| TreePos - position within tree (first location in the tree|
|
|
//| is TreeRoot + 1) |
|
|
//| CompressedSizes - not referenced if SaveCompressedSizes is |
|
|
//| False; otherwise, values computed by this |
|
|
//| function for specific values of TreePos are |
|
|
//| stored to CompressedSizes[TreePos - TreeRoot] |
|
|
//| (other elements of the array are not referenced)|
|
|
//| This array must be preallocated by caller. |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::CompressRec(CDecisionForest &df,
|
|
bool usemantissa8,
|
|
int treeroot,
|
|
int treepos,
|
|
CRowInt &compressedsizes,
|
|
CRowInt &buf,
|
|
int &dstoffs)
|
|
{
|
|
//--- create variables
|
|
int jmponbranch=0;
|
|
int child0size=0;
|
|
int child1size=0;
|
|
int varidx=0;
|
|
double leafval=0;
|
|
double splitval=0;
|
|
int dstoffsold=dstoffs;
|
|
//--- Leaf or split?
|
|
varidx=(int)MathRound(df.m_Trees[treepos]);
|
|
if(varidx==-1)
|
|
{
|
|
//--- Leaf node:
|
|
//--- * stream special value which denotes leaf (2*NVars)
|
|
//--- * then, stream scalar value (floating point) or class number (unsigned integer)
|
|
leafval=df.m_Trees[treepos+1];
|
|
StreamUint(buf,dstoffs,2*df.m_NVars);
|
|
if(df.m_NClasses==1)
|
|
StreamFloat(buf,usemantissa8,dstoffs,leafval);
|
|
else
|
|
StreamUint(buf,dstoffs,(int)MathRound(leafval));
|
|
}
|
|
else
|
|
{
|
|
//--- Split node:
|
|
//--- * fetch compressed sizes of child nodes, decide which child goes first
|
|
jmponbranch=(int)MathRound(df.m_Trees[treepos+2]);
|
|
splitval=df.m_Trees[treepos+1];
|
|
child0size=compressedsizes[treepos+m_InnerNodeWidth-treeroot];
|
|
child1size=compressedsizes[treeroot+jmponbranch-treeroot];
|
|
if(child0size<=child1size)
|
|
{
|
|
//--- Child #0 comes first because it is shorter:
|
|
//--- * stream variable index used for splitting;
|
|
//--- value in [0,NVars) range indicates that split is
|
|
//--- "if VAR<VAL then BRANCH0 else BRANCH1"
|
|
//--- * stream value used for splitting
|
|
//--- * stream children #0 and #1
|
|
StreamUint(buf,dstoffs,varidx);
|
|
StreamFloat(buf,usemantissa8,dstoffs,splitval);
|
|
StreamUint(buf,dstoffs,child0size);
|
|
CompressRec(df,usemantissa8,treeroot,treepos+m_InnerNodeWidth,compressedsizes,buf,dstoffs);
|
|
CompressRec(df,usemantissa8,treeroot,treeroot+jmponbranch,compressedsizes,buf,dstoffs);
|
|
}
|
|
else
|
|
{
|
|
//--- Child #1 comes first because it is shorter:
|
|
//--- * stream variable index used for splitting + NVars;
|
|
//--- value in [NVars,2*NVars) range indicates that split is
|
|
//--- "if VAR>=VAL then BRANCH0 else BRANCH1"
|
|
//--- * stream value used for splitting
|
|
//--- * stream children #0 and #1
|
|
StreamUint(buf,dstoffs,varidx+df.m_NVars);
|
|
StreamFloat(buf,usemantissa8,dstoffs,splitval);
|
|
StreamUint(buf,dstoffs,child1size);
|
|
CompressRec(df,usemantissa8,treeroot,treeroot+jmponbranch,compressedsizes,buf,dstoffs);
|
|
CompressRec(df,usemantissa8,treeroot,treepos+m_InnerNodeWidth,compressedsizes,buf,dstoffs);
|
|
}
|
|
}
|
|
//--- Integrity check at the end
|
|
if(!CAp::Assert(dstoffs-dstoffsold==compressedsizes[treepos-treeroot],__FUNCTION__": integrity check failed (compressed size at leaf)"))
|
|
return;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns exact number of bytes required to store |
|
|
//| compressed unsigned integer number(negative arguments result in |
|
|
//| assertion being generated). |
|
|
//+------------------------------------------------------------------+
|
|
int CDForest::ComputeCompressedUintSize(int v)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(v>=0))
|
|
return(0);
|
|
//--- create variables
|
|
int result=1;
|
|
//---
|
|
while(v>=128)
|
|
{
|
|
v=v/128;
|
|
result=result+1;
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function stores compressed unsigned integer number (negative|
|
|
//| arguments result in assertion being generated) to byte array at |
|
|
//| location Offs and increments Offs by number of bytes being stored|
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::StreamUint(CRowInt &buf,
|
|
int &offs,
|
|
int v)
|
|
{
|
|
int v0=0;
|
|
//--- check
|
|
if(!CAp::Assert(v>=0))
|
|
return;
|
|
//---
|
|
while(true)
|
|
{
|
|
//--- Save 7 least significant bits of V, use 8th bit as a flag which
|
|
//--- tells us whether subsequent 7-bit packages will be sent.
|
|
v0=v%128;
|
|
if(v>=128)
|
|
v0=v0+128;
|
|
buf.Set(offs,(uint)v0);
|
|
offs++;
|
|
v/=128;
|
|
if(v==0)
|
|
break;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function reads compressed unsigned integer number from byte |
|
|
//| array starting at location Offs and increments Offs by number of |
|
|
//| bytes being read. |
|
|
//+------------------------------------------------------------------+
|
|
int CDForest::UnstreamUint(CRowInt &buf,int &offs)
|
|
{
|
|
//--- create variables
|
|
int result=0;
|
|
int v0=0;
|
|
int p=1;
|
|
//---
|
|
while(true)
|
|
{
|
|
//--- Rad 7 bits of V, use 8th bit as a flag which tells us whether
|
|
//--- subsequent 7-bit packages will be received.
|
|
v0=buf[offs];
|
|
offs=offs+1;
|
|
result+=v0%128*p;
|
|
if(v0<128)
|
|
break;
|
|
p*=128;
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function stores compressed floating point number to byte |
|
|
//| array at location Offs and increments Offs by number of bytes |
|
|
//| being stored. Either 8-bit mantissa or 16-bit mantissa is used. |
|
|
//| The exponent is always 7 bits of exponent + sign. Values which |
|
|
//| do not fit into exponent range are truncated to fit. |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::StreamFloat(CRowInt &buf,
|
|
bool usemantissa8,
|
|
int &offs,
|
|
double v)
|
|
{
|
|
//--- create variables
|
|
int signbit=0;
|
|
int e=0;
|
|
int m=0;
|
|
double twopow30=0;
|
|
double twopowm30=0;
|
|
double twopow10=0;
|
|
double twopowm10=0;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(v),__FUNCTION__": V is not finite number"))
|
|
return;
|
|
//--- Special case: zero
|
|
if(v==0.0)
|
|
{
|
|
if(usemantissa8)
|
|
{
|
|
buf.Set(offs,0);
|
|
buf.Set(offs+1,0);
|
|
offs+=2;
|
|
}
|
|
else
|
|
{
|
|
buf.Set(offs,0);
|
|
buf.Set(offs+1,0);
|
|
buf.Set(offs+2,0);
|
|
offs+=3;
|
|
}
|
|
return;
|
|
}
|
|
//--- Handle sign
|
|
signbit=0;
|
|
if(v<0.0)
|
|
{
|
|
v=-v;
|
|
signbit=128;
|
|
}
|
|
//--- Compute exponent
|
|
twopow30=1073741824;
|
|
twopow10=1024;
|
|
twopowm30=1.0/twopow30;
|
|
twopowm10=1.0/twopow10;
|
|
e=0;
|
|
while(v>=twopow30)
|
|
{
|
|
v*=twopowm30;
|
|
e+=30;
|
|
}
|
|
while(v>=twopow10)
|
|
{
|
|
v*=twopowm10;
|
|
e+=10;
|
|
}
|
|
while(v>=1.0)
|
|
{
|
|
v*=0.5;
|
|
e++;
|
|
}
|
|
while(v<twopowm30)
|
|
{
|
|
v*=twopow30;
|
|
e-=30;
|
|
}
|
|
while(v<twopowm10)
|
|
{
|
|
v*=twopow10;
|
|
e-=10;
|
|
}
|
|
while(v<0.5)
|
|
{
|
|
v*=2;
|
|
e-=1;
|
|
}
|
|
if(!CAp::Assert(v>=0.5 && v<1.0,__FUNCTION__": integrity check failed"))
|
|
return;
|
|
//--- Handle exponent underflow/overflow
|
|
if(e<-63)
|
|
{
|
|
signbit=0;
|
|
e=0;
|
|
v=0;
|
|
}
|
|
if(e>63)
|
|
{
|
|
e=63;
|
|
v=1.0;
|
|
}
|
|
//--- Save to stream
|
|
if(usemantissa8)
|
|
{
|
|
m=(int)MathRound(v*256);
|
|
if(m==256)
|
|
{
|
|
m=m/2;
|
|
e=MathMin(e+1,63);
|
|
}
|
|
buf.Set(offs,e+64+signbit);
|
|
buf.Set(offs+1,m);
|
|
offs+=2;
|
|
}
|
|
else
|
|
{
|
|
m=(int)MathRound(v*65536);
|
|
if(m==65536)
|
|
{
|
|
m=m/2;
|
|
e=MathMin(e+1,63);
|
|
}
|
|
buf.Set(offs,e+64+signbit);
|
|
buf.Set(offs+1,m%256);
|
|
buf.Set(offs+2,m/256);
|
|
offs+=3;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function reads compressed floating point number from the |
|
|
//| byte array starting from location Offs and increments Offs by |
|
|
//| number of bytes being read. |
|
|
//| Either 8-bit mantissa or 16-bit mantissa is used. The exponent |
|
|
//| is always 7 bits of exponent + sign. Values which do not fit |
|
|
//| into exponent range are truncated to fit. |
|
|
//+------------------------------------------------------------------+
|
|
double CDForest::UnstreamFloat(CRowInt &buf,
|
|
bool usemantissa8,
|
|
int &offs)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int e=0;
|
|
double v=0;
|
|
double inv256=1.0/256.0;
|
|
//--- Read from stream
|
|
if(usemantissa8)
|
|
{
|
|
e=buf[offs];
|
|
v=buf[offs+1]*inv256;
|
|
offs+=2;
|
|
}
|
|
else
|
|
{
|
|
e=buf[offs];
|
|
v=(buf[offs+1]*inv256+buf[offs+2])*inv256;
|
|
offs+=3 ;
|
|
}
|
|
//--- Decode
|
|
if(e>128)
|
|
{
|
|
v=-v;
|
|
e=e-128;
|
|
}
|
|
e=e-64;
|
|
result=MathPow(2.0,e)*v;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Classification error |
|
|
//+------------------------------------------------------------------+
|
|
int CDForest::DFClsError(CDecisionForest &df,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
//--- create variables
|
|
int result=0;
|
|
int k=0;
|
|
int tmpi=0;
|
|
int i_=0;
|
|
//--- creating arrays
|
|
CRowDouble x;
|
|
CRowDouble y;
|
|
//--- check
|
|
if(df.m_NClasses<=1)
|
|
return(0);
|
|
//--- initialization
|
|
result=0;
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
//--- copy
|
|
x=xy[i]+0;
|
|
//--- function call
|
|
DFProcess(df,x,y);
|
|
//--- change values
|
|
k=(int)MathRound(xy.Get(i,df.m_NVars));
|
|
tmpi=0;
|
|
for(int j=1; j<df.m_NClasses; j++)
|
|
{
|
|
//--- check
|
|
if(y[j]>y[tmpi])
|
|
tmpi=j;
|
|
}
|
|
//--- check
|
|
if(tmpi!=k)
|
|
result++;
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine for processing one decision tree stored in |
|
|
//| uncompressed format starting at SubtreeRoot (this index points |
|
|
//| to the header of the tree, not its first node). First node being |
|
|
//| processed is located at NodeOffs. |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFProcessInternalUncompressed(CDecisionForest &df,
|
|
int subtreeroot,
|
|
int nodeoffs,
|
|
CRowDouble &x,
|
|
CRowDouble &y)
|
|
{
|
|
int idx=0;
|
|
//--- check
|
|
if(!CAp::Assert(df.m_ForestFormat==m_DFUncompressedV0,__FUNCTION__+": unexpected forest format"))
|
|
return;
|
|
//--- Navigate through the tree
|
|
while(true)
|
|
{
|
|
if(df.m_Trees[nodeoffs]==(-1.0))
|
|
{
|
|
if(df.m_NClasses==1)
|
|
y.Add(0,df.m_Trees[nodeoffs+1]);
|
|
else
|
|
{
|
|
idx=(int)MathRound(df.m_Trees[nodeoffs+1]);
|
|
y.Add(idx,1);
|
|
}
|
|
break;
|
|
}
|
|
if(x[(int)MathRound(df.m_Trees[nodeoffs])]<df.m_Trees[nodeoffs+1])
|
|
nodeoffs+=m_InnerNodeWidth;
|
|
else
|
|
nodeoffs=subtreeroot+(int)MathRound(df.m_Trees[nodeoffs+2]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine for processing one decision tree stored in |
|
|
//| compressed format starting at Offs (this index points to the |
|
|
//| first node of the tree, right past the header field). |
|
|
//+------------------------------------------------------------------+
|
|
void CDForest::DFProcessInternalCompressed(CDecisionForest &df,
|
|
int offs,
|
|
CRowDouble &x,
|
|
CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int leafindicator=0;
|
|
int varidx=0;
|
|
double splitval=0;
|
|
int jmplen=0;
|
|
double leafval=0;
|
|
int leafcls=0;
|
|
//--- check
|
|
if(!CAp::Assert(df.m_ForestFormat==m_DFCompressedV0,__FUNCTION__+": unexpected forest format"))
|
|
return;
|
|
//--- Navigate through the tree
|
|
leafindicator=2*df.m_NVars;
|
|
while(true)
|
|
{
|
|
//--- Read variable idx
|
|
varidx=UnstreamUint(df.m_Trees8,offs);
|
|
//--- Is it leaf?
|
|
if(varidx==leafindicator)
|
|
{
|
|
if(df.m_NClasses==1)
|
|
{
|
|
//--- Regression forest
|
|
leafval=UnstreamFloat(df.m_Trees8,df.m_UseMantissa8,offs);
|
|
y.Add(0,leafval);
|
|
}
|
|
else
|
|
{
|
|
//--- Classification forest
|
|
leafcls=UnstreamUint(df.m_Trees8,offs);
|
|
y.Add(leafcls,1);
|
|
}
|
|
break;
|
|
}
|
|
//--- Process node
|
|
splitval=UnstreamFloat(df.m_Trees8,df.m_UseMantissa8,offs);
|
|
jmplen=UnstreamUint(df.m_Trees8,offs);
|
|
if(varidx<df.m_NVars)
|
|
{
|
|
//--- The split rule is "if VAR<VAL then BRANCH0 else BRANCH1"
|
|
if(x[varidx]>=splitval)
|
|
offs+=jmplen;
|
|
}
|
|
else
|
|
{
|
|
//--- The split rule is "if VAR>=VAL then BRANCH0 else BRANCH1"
|
|
varidx-=df.m_NVars;
|
|
if(x[varidx]<splitval)
|
|
offs+=jmplen;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Middle and clusterization |
|
|
//+------------------------------------------------------------------+
|
|
class CKMeans
|
|
{
|
|
public:
|
|
static void KMeansGenerate(CMatrixDouble &xy,const int npoints,const int nvars,const int k,const int restarts,int &info,CMatrixDouble &c,int &xyc[]);
|
|
|
|
private:
|
|
static bool SelectCenterPP(CMatrixDouble &xy,const int npoints,const int nvars,CMatrixDouble ¢ers,bool &cbusycenters[],const int ccnt,double &d2[],double &p[],double &tmp[]);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| 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 |
|
|
//+------------------------------------------------------------------+
|
|
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);
|
|
ArrayResize(d2,npoints);
|
|
ArrayResize(p,npoints);
|
|
ArrayResize(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.Set(0,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; i++)
|
|
xyc[i]=-1;
|
|
//--- cycle
|
|
while(true)
|
|
{
|
|
//--- fill XYC with center numbers
|
|
waschanges=false;
|
|
for(i=0; i<npoints; 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.Set(i,j,0);
|
|
}
|
|
//--- change values
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
csizes[xyc[i]]=csizes[xyc[i]]+1;
|
|
for(i_=0; i_<=nvars-1; i_++)
|
|
ct.Set(xyc[i],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.Set(j,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; 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; 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; i++)
|
|
xyc[i]=xycbest[i];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Select center for a new cluster using k-means++ rule |
|
|
//+------------------------------------------------------------------+
|
|
bool CKMeans::SelectCenterPP(CMatrixDouble &xy,const int npoints,
|
|
const int nvars,CMatrixDouble ¢ers,
|
|
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; 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; i++)
|
|
s=s+d2[i];
|
|
//--- check
|
|
if(s==0.0)
|
|
return(false);
|
|
//--- change value
|
|
s=1/s;
|
|
for(i_=0; i_<npoints; 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; i++)
|
|
{
|
|
s=s+p[i];
|
|
//--- check
|
|
if(v<=s || i==npoints-1)
|
|
{
|
|
for(i_=0; i_<=nvars-1; i_++)
|
|
centers.Set(cc,i_,xy[i][i_]);
|
|
busycenters[cc]=true;
|
|
//--- break the cycle
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Multiclass Fisher LDA |
|
|
//+------------------------------------------------------------------+
|
|
class CLDA
|
|
{
|
|
public:
|
|
static void FisherLDA(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,int &info,double &w[]);
|
|
static void FisherLDA(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,int &info,CRowDouble &w);
|
|
static void FisherLDAN(CMatrixDouble &xy,const int npoints,const int nvars,const int nclasses,int &info,CMatrixDouble &w);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| 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] |
|
|
//+------------------------------------------------------------------+
|
|
void CLDA::FisherLDA(CMatrixDouble &xy,const int npoints,
|
|
const int nvars,const int nclasses,
|
|
int &info,double &w[])
|
|
{
|
|
CRowDouble W;
|
|
FisherLDA(xy,npoints,nvars,nclasses,info,W);
|
|
W.ToArray(w);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CLDA::FisherLDA(CMatrixDouble &xy,const int npoints,
|
|
const int nvars,const int nclasses,
|
|
int &info,CRowDouble &w)
|
|
{
|
|
//--- create matrix
|
|
CMatrixDouble w2;
|
|
//--- initialization
|
|
info=0;
|
|
//--- function call
|
|
FisherLDAN(xy,npoints,nvars,nclasses,info,w2);
|
|
//--- check and copy
|
|
if(info>0)
|
|
w=w2.Col(0)+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) |
|
|
//+------------------------------------------------------------------+
|
|
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[];
|
|
vector<double> mu;
|
|
int nc[];
|
|
vector<double> tf;
|
|
CRowDouble d;
|
|
CRowDouble d2;
|
|
CRowDouble work;
|
|
//--- create matrix
|
|
CMatrixDouble muc;
|
|
CMatrixDouble sw;
|
|
CMatrixDouble st;
|
|
CMatrixDouble z;
|
|
CMatrixDouble z2;
|
|
CMatrixDouble tm;
|
|
CMatrixDouble sbroot;
|
|
CMatrixDouble a;
|
|
CMatrixDouble xyc;
|
|
CMatrixDouble xyproj;
|
|
CMatrixDouble wproj;
|
|
//--- initialization
|
|
info=0;
|
|
//--- Test data
|
|
if((npoints<0 || nvars<1) || nclasses<2)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
mu=xy.Col(nvars);
|
|
mu.Resize(npoints);
|
|
//--- check
|
|
if((int)MathRound(mu.Min())<0 || (int)MathRound(mu.Max())>=nclasses)
|
|
{
|
|
info=-2;
|
|
return;
|
|
}
|
|
//--- change value
|
|
info=1;
|
|
//--- Special case: NPoints<=1
|
|
//--- Degenerate task.
|
|
if(npoints<=1)
|
|
{
|
|
info=2;
|
|
//--- initialization
|
|
w=matrix<double>::Identity(nvars,nvars);
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- Prepare temporaries
|
|
tf.Resize(nvars);
|
|
work.Resize(MathMax(nvars,npoints)+1);
|
|
//--- Convert class labels from reals to integers (just for convenience)
|
|
ArrayResizeAL(c,npoints);
|
|
for(i=0; i<npoints; i++)
|
|
c[i]=(int)MathRound(xy.Get(i,nvars));
|
|
//--- Calculate class sizes and means
|
|
mu=xy.Sum(0);
|
|
mu.Resize(nvars);
|
|
muc=matrix<double>::Zeros(nclasses,nvars+1);
|
|
ArrayResize(nc,nclasses);
|
|
ArrayInitialize(nc,0);
|
|
//--- calculation
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
for(i_=0; i_<nvars; i_++)
|
|
muc.Add(c[i],i_,xy.Get(i,i_));
|
|
nc[c[i]]++;
|
|
}
|
|
muc.Resize(nclasses,nvars);
|
|
for(i=0; i<nclasses; i++)
|
|
muc.Row(i,muc[i]/(double)nc[i]);
|
|
//--- change values
|
|
mu=mu/(double)npoints;
|
|
//--- Create ST matrix
|
|
xyc=xy.Transpose()+0;
|
|
xyc.Resize(nvars,npoints);
|
|
for(k=0; k<nvars; k++)
|
|
xyc.Row(k,xyc[k]-mu[k]);
|
|
st=xyc.MatMul(xyc,true)+0;
|
|
//--- Create SW matrix
|
|
xyc=xy.Transpose()+0;
|
|
xyc.Resize(nvars,npoints);
|
|
for(k=0; k<npoints; k++)
|
|
xyc.Col(k,xyc.Col(k)-muc[c[k]]);
|
|
sw=xyc.MatMul(xyc,true)+0;
|
|
//--- 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;
|
|
w=matrix<double>::Identity(nvars,nvars);
|
|
//--- 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; 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; i++)
|
|
xyproj.Set(i,nvars-m,xy.Get(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; k++)
|
|
w.Col(k,z.Col(k-nvars+m)+0);
|
|
info=2;
|
|
}
|
|
else
|
|
{
|
|
//--- General case: no multicollinearity
|
|
tm.Resize(nvars,nvars);
|
|
a.Resize(nvars,nvars);
|
|
//--- function call
|
|
tm=sw.MatMul(z,false)+0;
|
|
a=z.Transpose()+0;
|
|
a=a.MatMul(tm,false)+0;
|
|
//--- change values
|
|
for(i=0; i<nvars; i++)
|
|
for(j=0; j<nvars; j++)
|
|
a.Mul(i,j,1.0/MathSqrt(d[i]*d[j]));
|
|
//--- check
|
|
if(!CEigenVDetect::SMatrixEVD(a,nvars,1,true,d2,z2))
|
|
{
|
|
info=-4;
|
|
return;
|
|
}
|
|
//--- calculation
|
|
for(k=0; k<nvars; k++)
|
|
z2.Row(k,z2[k]/MathSqrt(d[k]));
|
|
w=z.MatMul(z2,false)+0;
|
|
}
|
|
//--- Post-processing:
|
|
//--- * normalization
|
|
//--- * converting to non-negative form,if possible
|
|
for(k=0; k<nvars; k++)
|
|
{
|
|
//--- calculation
|
|
v=MathPow(w.Col(k)+0,2.0).Sum();
|
|
w.Col(k,w.Col(k)/MathSqrt(v));
|
|
v=(w.Col(k)+0).Sum();
|
|
//--- check
|
|
if(v<0.0)
|
|
w.Col(k,w.Col(k)*(-1.0));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary class for CLinReg |
|
|
//+------------------------------------------------------------------+
|
|
class CLinearModel
|
|
{
|
|
public:
|
|
CRowDouble m_w;
|
|
//--- constructor, destructor
|
|
CLinearModel(void) {}
|
|
~CLinearModel(void) {}
|
|
//--- copy
|
|
void Copy(const CLinearModel &obj) { m_w=obj.m_w; }
|
|
//--- overloading
|
|
void operator=(const CLinearModel &obj) { Copy(obj); }
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| This class is a shell for class CLinearModel |
|
|
//+------------------------------------------------------------------+
|
|
class CLinearModelShell
|
|
{
|
|
private:
|
|
CLinearModel m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CLinearModelShell(void) {}
|
|
CLinearModelShell(CLinearModel &obj) { m_innerobj.Copy(obj); }
|
|
~CLinearModelShell(void) {}
|
|
//--- method
|
|
CLinearModel *GetInnerObj(void) { 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
|
|
CRowInt m_cvdefects;
|
|
//--- matrix
|
|
CMatrixDouble m_c;
|
|
//--- constructor, destructor
|
|
CLRReport(void);
|
|
~CLRReport(void) {}
|
|
//--- copy
|
|
void Copy(const CLRReport &obj);
|
|
//--- overloading
|
|
void operator=(const CLRReport &obj) { Copy(obj); }
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CLRReport::CLRReport(void)
|
|
{
|
|
//--- copy variables
|
|
m_RMSError=0;
|
|
m_AvgError=0;
|
|
m_AvgRelError=0;
|
|
m_cvrmserror=0;
|
|
m_cvavgerror=0;
|
|
m_cvavgrelerror=0;
|
|
m_ncvdefects=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CLRReport::Copy(const 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
|
|
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) { m_innerobj.Copy(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);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
double CLRReportShell::GetRMSError(void)
|
|
{
|
|
return(m_innerobj.m_RMSError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
void CLRReportShell::SetRMSError(const double d)
|
|
{
|
|
m_innerobj.m_RMSError=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
double CLRReportShell::GetAvgError(void)
|
|
{
|
|
return(m_innerobj.m_AvgError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
void CLRReportShell::SetAvgError(const double d)
|
|
{
|
|
m_innerobj.m_AvgError=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
double CLRReportShell::GetAvgRelError(void)
|
|
{
|
|
return(m_innerobj.m_AvgRelError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
void CLRReportShell::SetAvgRelError(const double d)
|
|
{
|
|
m_innerobj.m_AvgRelError=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable cvrmserror |
|
|
//+------------------------------------------------------------------+
|
|
double CLRReportShell::GetCVRMSError(void)
|
|
{
|
|
return(m_innerobj.m_cvrmserror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable cvrmserror |
|
|
//+------------------------------------------------------------------+
|
|
void CLRReportShell::SetCVRMSError(const double d)
|
|
{
|
|
m_innerobj.m_cvrmserror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable cvavgerror |
|
|
//+------------------------------------------------------------------+
|
|
double CLRReportShell::GetCVAvgError(void)
|
|
{
|
|
return(m_innerobj.m_cvavgerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable cvavgerror |
|
|
//+------------------------------------------------------------------+
|
|
void CLRReportShell::SetCVAvgError(const double d)
|
|
{
|
|
m_innerobj.m_cvavgerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable cvavgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
double CLRReportShell::GetCVAvgRelError(void)
|
|
{
|
|
return(m_innerobj.m_cvavgrelerror);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable cvavgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
void CLRReportShell::SetCVAvgRelError(const double d)
|
|
{
|
|
m_innerobj.m_cvavgrelerror=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable ncvdefects |
|
|
//+------------------------------------------------------------------+
|
|
int CLRReportShell::GetNCVDEfects(void)
|
|
{
|
|
return(m_innerobj.m_ncvdefects);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable ncvdefects |
|
|
//+------------------------------------------------------------------+
|
|
void CLRReportShell::SetNCVDEfects(const int i)
|
|
{
|
|
m_innerobj.m_ncvdefects=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CLRReport *CLRReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Linear regression class |
|
|
//+------------------------------------------------------------------+
|
|
class CLinReg
|
|
{
|
|
public:
|
|
//--- constant
|
|
static const int m_lrvnum;
|
|
//--- 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 LRBuildS(CMatrixDouble &xy,CRowDouble &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 LRBuildZS(CMatrixDouble &xy,CRowDouble &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 LRUnpack(CLinearModel &lm,CRowDouble &v,int &nvars);
|
|
static void LRPack(double &v[],const int nvars,CLinearModel &lm);
|
|
static void LRPack(CRowDouble &v,const int nvars,CLinearModel &lm);
|
|
static double LRProcess(CLinearModel &lm,double &x[]);
|
|
static double LRProcess(CLinearModel &lm,CRowDouble &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 LRLines(CMatrixDouble &xy,CRowDouble &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);
|
|
|
|
private:
|
|
static void LRInternal(CMatrixDouble &xy,CRowDouble &s,const int npoints,const int nvars,int &info,CLinearModel &lm,CLRReport &ar);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize constant |
|
|
//+------------------------------------------------------------------+
|
|
const int CLinReg::m_lrvnum=5;
|
|
//+------------------------------------------------------------------+
|
|
//| 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 |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRBuild(CMatrixDouble &xy,const int npoints,const int nvars,
|
|
int &info,CLinearModel &lm,CLRReport &ar)
|
|
{
|
|
//--- initialization
|
|
info=0;
|
|
//--- check
|
|
if(npoints<=nvars+1 || nvars<1)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
//--- create variables
|
|
int i=0;
|
|
double sigma2=0;
|
|
int i_=0;
|
|
//--- create array
|
|
CRowDouble s;
|
|
//--- allocation
|
|
s=vector<double>::Ones(npoints);
|
|
//--- 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);
|
|
ar.m_c*=sigma2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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 |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRBuildS(CMatrixDouble &xy,double &s[],const int npoints,
|
|
const int nvars,int &info,CLinearModel &lm,
|
|
CLRReport &ar)
|
|
{
|
|
CRowDouble S=s;
|
|
LRBuildS(xy,S,npoints,nvars,info,lm,ar);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRBuildS(CMatrixDouble &xy,CRowDouble &s,const int npoints,
|
|
const int nvars,int &info,CLinearModel &lm,
|
|
CLRReport &ar)
|
|
{
|
|
//--- Test parameters
|
|
if(npoints<=nvars+1 || nvars<1)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
//--- create variables
|
|
double v=0;
|
|
int offs=0;
|
|
double mean=0;
|
|
double variance=0;
|
|
double skewness=0;
|
|
double kurtosis=0;
|
|
int i_=0;
|
|
//--- initialization
|
|
info=0;
|
|
//--- creating arrays
|
|
vector<double> means;
|
|
vector<double> sigmas=vector<double>::Zeros(nvars);
|
|
CRowDouble x;
|
|
//--- create array
|
|
CMatrixDouble xyi;
|
|
//--- Copy data,add one more column (constant term)
|
|
xyi=xy;
|
|
xyi.Resize(npoints,nvars+2);
|
|
xyi.Col(nvars+1,vector<double>::Ones(npoints));
|
|
xyi.SwapCols(nvars,nvars+1);
|
|
//--- Standartization
|
|
means=xyi.Mean(0);
|
|
means.Resize(nvars);
|
|
for(int i=0; i<nvars; i++)
|
|
{
|
|
x=xyi.Col(i)+0;
|
|
//--- function call
|
|
sigmas[i]=MathSqrt(CBaseStat::SampleVariance(x,npoints));
|
|
if(sigmas[i]==0.0)
|
|
sigmas[i]=1.0;
|
|
xyi.Col(i,(xyi.Col(i)-means[i])/sigmas[i]);
|
|
}
|
|
//--- 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(int j=0; j<nvars; j++)
|
|
{
|
|
//--- Constant term is updated (and its covariance too,
|
|
//--- since it gets some variance from J-th component)
|
|
v=means[j]/sigmas[j];
|
|
lm.m_w.Add(offs+nvars,-lm.m_w[offs+j]*v);
|
|
ar.m_c.Row(nvars,ar.m_c.Row(nvars)-ar.m_c.Row(j)*v);
|
|
ar.m_c.Col(nvars,ar.m_c.Col(nvars)-ar.m_c.Col(j)*v);
|
|
//--- J-th term is updated
|
|
v=1/sigmas[j];
|
|
lm.m_w.Mul(offs+j,v);
|
|
ar.m_c.Row(j,ar.m_c[j]*v);
|
|
ar.m_c.Col(j,ar.m_c.Col(j)*v);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Like LRBuildS, but builds model |
|
|
//| Y=A(0)*X[0] + ... + A(N-1)*X[N-1] |
|
|
//| i.m_e. with zero constant term. |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRBuildZS(CMatrixDouble &xy,double &s[],const int npoints,
|
|
const int nvars,int &info,CLinearModel &lm,
|
|
CLRReport &ar)
|
|
{
|
|
CRowDouble S=s;
|
|
LRBuildZS(xy,S,npoints,nvars,info,lm,ar);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRBuildZS(CMatrixDouble &xy,CRowDouble &s,const int npoints,
|
|
const int nvars,int &info,CLinearModel &lm,
|
|
CLRReport &ar)
|
|
{
|
|
//--- Test parameters
|
|
if(npoints<=nvars+1 || nvars<1)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
//--- create variables
|
|
double v=0;
|
|
int offs=0;
|
|
double mean=0;
|
|
double variance=0;
|
|
double skewness=0;
|
|
double kurtosis=0;
|
|
//--- initialization
|
|
info=0;
|
|
//--- creating arrays
|
|
CRowDouble x;
|
|
CRowDouble c;
|
|
//--- create matrix
|
|
CMatrixDouble xyi;
|
|
//--- Copy data,add one more column (constant term)
|
|
xyi=xy;
|
|
xyi.Resize(npoints,nvars+2);
|
|
xyi.Col(nvars+1,vector<double>::Zeros(npoints));
|
|
xyi.SwapCols(nvars,nvars+1);
|
|
//--- Standartization: unusual scaling
|
|
c.Resize(nvars);
|
|
for(int j=0; j<nvars; j++)
|
|
{
|
|
x=xy.Col(j)+0;
|
|
//--- 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.Set(j,mean);
|
|
}
|
|
else
|
|
{
|
|
//--- variation is large,it is better to bring variance to 1
|
|
if(variance==0.0)
|
|
c.Set(j,1.0);
|
|
else
|
|
c.Set(j,MathSqrt(variance));
|
|
}
|
|
xyi.Col(j,x/c[j]+0);
|
|
}
|
|
//--- 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(int j=0; j<nvars; j++)
|
|
{
|
|
//--- J-th term is updated
|
|
v=1/c[j];
|
|
lm.m_w.Mul(offs+j,v);
|
|
ar.m_c.Row(j,ar.m_c[j]*v);
|
|
ar.m_c.Col(j,ar.m_c.Col(j)*v);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Like LRBuild but builds model |
|
|
//| Y=A(0)*X[0] + ... + A(N-1)*X[N-1] |
|
|
//| i.m_e. with zero constant term. |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRBuildZ(CMatrixDouble &xy,const int npoints,const int nvars,
|
|
int &info,CLinearModel &lm,CLRReport &ar)
|
|
{
|
|
//--- check
|
|
if(npoints<=nvars+1 || nvars<1)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
//--- create array
|
|
CRowDouble s=vector<double>::Ones(npoints);
|
|
//--- initialization
|
|
info=0;
|
|
//--- function call
|
|
LRBuildZS(xy,s,npoints,nvars,info,lm,ar);
|
|
//--- check
|
|
if(info<0)
|
|
return;
|
|
//--- calculation
|
|
double sigma2=CMath::Sqr(ar.m_RMSError)*npoints/(npoints-nvars-1);
|
|
ar.m_c*=sigma2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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) |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRUnpack(CLinearModel &lm,double &v[],int &nvars)
|
|
{
|
|
CRowDouble V;
|
|
LRUnpack(lm,V,nvars);
|
|
V.ToArray(v);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRUnpack(CLinearModel &lm,CRowDouble &v,int &nvars)
|
|
{
|
|
//--- create variables
|
|
int offs=0;
|
|
int i1_=0;
|
|
//--- initialization
|
|
nvars=0;
|
|
//--- check
|
|
if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
|
|
{
|
|
v.Resize(0);
|
|
return;
|
|
}
|
|
//--- change values
|
|
nvars=(int)MathRound(lm.m_w[2]);
|
|
offs=(int)MathRound(lm.m_w[3]);
|
|
//--- allocation
|
|
v.Resize(nvars+1);
|
|
//--- calculation
|
|
i1_=offs;
|
|
for(int i_=0; i_<=nvars; i_++)
|
|
v.Set(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. |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRPack(double &v[],const int nvars,CLinearModel &lm)
|
|
{
|
|
CRowDouble V=v;
|
|
LRPack(V,nvars,lm);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRPack(CRowDouble &v,const int nvars,CLinearModel &lm)
|
|
{
|
|
//--- create variables
|
|
int offs=0;
|
|
int i1_=0;
|
|
//--- allocation
|
|
lm.m_w.Resize(5+nvars);
|
|
//--- change values
|
|
offs=4;
|
|
lm.m_w.Set(0,4+nvars+1);
|
|
lm.m_w.Set(1,m_lrvnum);
|
|
lm.m_w.Set(2,nvars);
|
|
lm.m_w.Set(3,offs);
|
|
//--- calculation
|
|
i1_=-offs;
|
|
for(int i_=offs; i_<=offs+nvars; i_++)
|
|
lm.m_w.Set(i_,v[i_+i1_]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Procesing |
|
|
//| INPUT PARAMETERS: |
|
|
//| LM - linear model |
|
|
//| X - input vector, array[0..NVars-1]. |
|
|
//| Result: |
|
|
//| value of linear model regression estimate |
|
|
//+------------------------------------------------------------------+
|
|
double CLinReg::LRProcess(CLinearModel &lm,double &x[])
|
|
{
|
|
CRowDouble X=x;
|
|
return(LRProcess(lm,X));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
double CLinReg::LRProcess(CLinearModel &lm,CRowDouble &x)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
int offs=0;
|
|
int nvars=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(int i_=0; i_<nvars; 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. |
|
|
//+------------------------------------------------------------------+
|
|
double CLinReg::LRRMSError(CLinearModel &lm,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
|
|
return(EMPTY_VALUE);
|
|
//--- create variables
|
|
double result=0;
|
|
double v=0;
|
|
int nvars=(int)MathRound(lm.m_w[2]);
|
|
CRowDouble x;
|
|
//--- calculation
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
x=xy[i]+0;
|
|
v=LRProcess(lm,x);
|
|
result+=CMath::Sqr(v-xy.Get(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. |
|
|
//+------------------------------------------------------------------+
|
|
double CLinReg::LRAvgError(CLinearModel &lm,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
|
|
return(EMPTY_VALUE);
|
|
//--- create variables
|
|
double result=0;
|
|
double v=0;
|
|
int nvars=(int)MathRound(lm.m_w[2]);
|
|
CRowDouble x;
|
|
//--- calculation
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
x=xy[i]+0;
|
|
v=LRProcess(lm,x);
|
|
result+=MathAbs(v-xy.Get(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. |
|
|
//+------------------------------------------------------------------+
|
|
double CLinReg::LRAvgRelError(CLinearModel &lm,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert((int)MathRound(lm.m_w[1])==m_lrvnum,__FUNCTION__+": Incorrect LINREG version!"))
|
|
return(EMPTY_VALUE);
|
|
//--- create variables
|
|
double result=0;
|
|
int k=0;
|
|
double v=0;
|
|
double y=0;
|
|
int nvars=(int)MathRound(lm.m_w[2]);
|
|
CRowDouble x;
|
|
//--- calculation
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
y=xy.Get(i,nvars);
|
|
//--- check
|
|
if(y==0.0)
|
|
continue;
|
|
//--- calculation
|
|
x=xy[i]+0;
|
|
v=LRProcess(lm,x);
|
|
//--- get result
|
|
result+=MathAbs((v-y)/y);
|
|
k++;
|
|
}
|
|
//--- check
|
|
if(k!=0)
|
|
result/=k;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copying of LinearModel strucure |
|
|
//| INPUT PARAMETERS: |
|
|
//| LM1 - original |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| LM2 - copy |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRCopy(CLinearModel &lm1,CLinearModel &lm2)
|
|
{
|
|
lm2.m_w=lm1.m_w;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Class method |
|
|
//+------------------------------------------------------------------+
|
|
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)
|
|
{
|
|
CRowDouble S=s;
|
|
LRLines(xy,S,n,info,a,b,vara,varb,covab,corrab,p);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRLines(CMatrixDouble &xy,CRowDouble &s,const int n,
|
|
int &info,double &a,double &b,double &vara,
|
|
double &varb,double &covab,double &corrab,
|
|
double &p)
|
|
{
|
|
//--- initialization
|
|
info=0;
|
|
a=0;
|
|
b=0;
|
|
vara=0;
|
|
varb=0;
|
|
covab=0;
|
|
corrab=0;
|
|
p=0;
|
|
//--- check
|
|
if(n<2)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
if(s.Min()<=0.0)
|
|
{
|
|
info=-2;
|
|
return;
|
|
}
|
|
//--- create variables
|
|
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;
|
|
//--- change value
|
|
info=1;
|
|
//--- Calculate S,SX,SY,SXX
|
|
vector<double> X=xy.Col(0);
|
|
vector<double> Y=xy.Col(1);
|
|
vector<double> S=s.ToVector();
|
|
S.Resize(n);
|
|
X.Resize(n);
|
|
Y.Resize(n);
|
|
vector<double> SS=MathPow(S*S,-1);
|
|
ss=SS.Sum();
|
|
sx=(X*SS).Sum();
|
|
sy=(Y*SS).Sum();
|
|
sxx=(X*X*SS).Sum();
|
|
//--- 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
|
|
vector<double> T=(X-sx/ss)/S;
|
|
b=((T*Y)/S).Sum();
|
|
stt=(T*T).Sum();
|
|
b=b/stt;
|
|
a=(sy-sx*b)/ss;
|
|
//--- Calculate goodness-of-fit
|
|
if(n>2)
|
|
{
|
|
chi2=(MathPow((Y-X*b-a)/S,2)).Sum();
|
|
//--- function call
|
|
p=CIncGammaF::IncompleteGammaC((double)(n-2)/2.0,chi2/2.0);
|
|
}
|
|
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 |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRLine(CMatrixDouble &xy,const int n,int &info,
|
|
double &a,double &b)
|
|
{
|
|
//--- initialization
|
|
info=0;
|
|
a=0;
|
|
b=0;
|
|
//--- check
|
|
if(n<2)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
//--- create variables
|
|
double vara=0;
|
|
double varb=0;
|
|
double covab=0;
|
|
double corrab=0;
|
|
double p=0;
|
|
//--- create array
|
|
CRowDouble s=vector<double>::Ones(n);
|
|
//--- function call
|
|
LRLines(xy,s,n,info,a,b,vara,varb,covab,corrab,p);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal linear regression subroutine |
|
|
//+------------------------------------------------------------------+
|
|
void CLinReg::LRInternal(CMatrixDouble &xy,CRowDouble &s,const int npoints,
|
|
const int nvars,int &info,CLinearModel &lm,
|
|
CLRReport &ar)
|
|
{
|
|
//--- Check for errors in data
|
|
if(npoints<nvars || nvars<1)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
if(s.Min()<=0.0)
|
|
{
|
|
info=-2;
|
|
return;
|
|
}
|
|
//--- create variables
|
|
CMatrixDouble a;
|
|
CMatrixDouble u;
|
|
CMatrixDouble vt;
|
|
CMatrixDouble vm;
|
|
CMatrixDouble xym;
|
|
CRowDouble b;
|
|
CRowDouble sv;
|
|
CRowDouble t;
|
|
CRowDouble svi;
|
|
CRowDouble work;
|
|
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=1000;
|
|
CLRReport ar2;
|
|
int offs=0;
|
|
CLinearModel tlm;
|
|
int i_=0;
|
|
int i1_=0;
|
|
//--- Check for errors in data
|
|
info=1;
|
|
//--- Create design matrix
|
|
a.Resize(npoints,nvars);
|
|
b.Resize(npoints);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
r=1/s[i];
|
|
for(i_=0; i_<=nvars-1; i_++)
|
|
a.Set(i,i_,r*xy.Get(i,i_));
|
|
b.Set(i,xy.Get(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
|
|
lm.m_w.Resize(4+nvars);
|
|
offs=4;
|
|
lm.m_w.Set(0,4+nvars);
|
|
lm.m_w.Set(1,m_lrvnum);
|
|
lm.m_w.Set(2,nvars-1);
|
|
lm.m_w.Set(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
|
|
t.Resize(nvars);
|
|
svi.Resize(nvars);
|
|
ar.m_c.Resize(nvars,nvars);
|
|
vm.Resize(nvars,nvars);
|
|
if(!CSingValueDecompose::RMatrixSVD(a,npoints,nvars,1,1,2,sv,u,vt))
|
|
{
|
|
info=-4;
|
|
return;
|
|
}
|
|
if(sv[0]<=0.0)
|
|
{
|
|
//--- Degenerate case: zero design matrix.
|
|
for(i=offs; i<offs+nvars; i++)
|
|
lm.m_w.Set(i,0);
|
|
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;
|
|
ar.m_cvdefects.Resize(nvars);
|
|
ar.m_cvdefects.Fill(-1);
|
|
ar.m_c=matrix<double>::Zeros(nvars,nvars);
|
|
return;
|
|
}
|
|
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--)
|
|
{
|
|
if(sv[k-1]>(epstol*CMath::m_machineepsilon*sv[0]))
|
|
{
|
|
//--- Reduce
|
|
xym.Resize(npoints,k+1);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
for(j=0; j<k; j++)
|
|
{
|
|
r=CAblasF::RDotRR(nvars,xy,i,vt,j);
|
|
xym.Set(i,j,r);
|
|
}
|
|
xym.Set(i,k,xy.Get(i,nvars));
|
|
}
|
|
//--- Solve
|
|
LRInternal(xym,s,npoints,k,info,tlm,ar2);
|
|
if(info!=1)
|
|
return;
|
|
//--- Convert back to un-reduced format
|
|
for(j=0; j<nvars; j++)
|
|
lm.m_w.Set(offs+j,0);
|
|
for(j=0; j<k; j++)
|
|
{
|
|
r=tlm.m_w[offs+j];
|
|
i1_=-offs;
|
|
for(i_=offs; i_<offs+nvars; i_++)
|
|
lm.m_w.Add(i_,r*vt.Get(j,i_+i1_));
|
|
}
|
|
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;
|
|
ar.m_cvdefects.Resize(nvars);
|
|
for(j=0; j<ar.m_ncvdefects; j++)
|
|
ar.m_cvdefects.Set(j,ar2.m_cvdefects[j]);
|
|
for(j=ar.m_ncvdefects; j<nvars; j++)
|
|
ar.m_cvdefects.Set(j,-1);
|
|
ar.m_c.Resize(nvars,nvars);
|
|
work.Resize(nvars+1);
|
|
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);
|
|
return;
|
|
}
|
|
}
|
|
info=-255;
|
|
return;
|
|
}
|
|
for(i=0; i<nvars; i++)
|
|
{
|
|
if(sv[i]>(epstol*CMath::m_machineepsilon*sv[0]))
|
|
svi.Set(i,1/sv[i]);
|
|
else
|
|
svi.Set(i,0);
|
|
}
|
|
t=vector<double>::Zeros(nvars);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
r=b[i];
|
|
for(i_=0; i_<nvars; i_++)
|
|
t.Add(i_,r*u.Get(i,i_));
|
|
}
|
|
for(i=0; i<nvars; i++)
|
|
lm.m_w.Set(offs+i,0);
|
|
for(i=0; i<=nvars-1; i++)
|
|
{
|
|
r=t[i]*svi[i];
|
|
i1_=-offs;
|
|
for(i_=offs; i_<offs+nvars; i_++)
|
|
lm.m_w.Add(i_,r*vt.Get(i,i_+i1_));
|
|
}
|
|
for(j=0; j<=nvars-1; j++)
|
|
{
|
|
r=svi[j];
|
|
vm.Col(j,vt[j]*r);
|
|
}
|
|
for(i=0; i<nvars; i++)
|
|
{
|
|
for(j=i; j<nvars; j++)
|
|
{
|
|
r=CAblasF::RDotRR(nvars,vm,i,vm,j);
|
|
ar.m_c.Set(i,j,r);
|
|
ar.m_c.Set(j,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;
|
|
ar.m_cvdefects.Resize(nvars);
|
|
ar.m_cvdefects.Fill(-1,0,nvars);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
//--- Error on a training set
|
|
i1_=offs;
|
|
r=0.0;
|
|
for(i_=0; i_<nvars; i_++)
|
|
r+=xy.Get(i,i_)*lm.m_w[i_+i1_];
|
|
ar.m_RMSError+=CMath::Sqr(r-xy.Get(i,nvars));
|
|
ar.m_AvgError+=MathAbs(r-xy.Get(i,nvars));
|
|
if(xy.Get(i,nvars)!=0.0)
|
|
{
|
|
ar.m_AvgRelError+=MathAbs((r-xy.Get(i,nvars))/xy.Get(i,nvars));
|
|
na++;
|
|
}
|
|
//--- Error using fast leave-one-out cross-validation
|
|
p=CAblasF::RDotRR(nvars,u,i,u,i);
|
|
if(p>(1-epstol*CMath::m_machineepsilon))
|
|
{
|
|
ar.m_cvdefects.Set(ar.m_ncvdefects,i);
|
|
ar.m_ncvdefects++;
|
|
continue;
|
|
}
|
|
r=s[i]*(r/s[i]-b[i]*p)/(1-p);
|
|
ar.m_cvrmserror+=CMath::Sqr(r-xy.Get(i,nvars));
|
|
ar.m_cvavgerror+=MathAbs(r-xy.Get(i,nvars));
|
|
if(xy.Get(i,nvars)!=0.0)
|
|
{
|
|
ar.m_cvavgrelerror+=MathAbs((r-xy.Get(i,nvars))/xy.Get(i,nvars));
|
|
nacv++;
|
|
}
|
|
ncv++;
|
|
}
|
|
if(ncv==0)
|
|
{
|
|
//--- Something strange: ALL ui are degenerate.
|
|
//--- Unexpected...
|
|
info=-255;
|
|
return;
|
|
}
|
|
|
|
ar.m_RMSError=MathSqrt(ar.m_RMSError/npoints);
|
|
ar.m_AvgError=ar.m_AvgError/npoints;
|
|
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;
|
|
if(nacv!=0)
|
|
ar.m_cvavgrelerror=ar.m_cvavgrelerror/nacv;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Model's errors: |
|
|
//| * RelCLSError - fraction of misclassified cases. |
|
|
//| * AvgCE - acerage cross-entropy |
|
|
//| * RMSError - root-mean-square error |
|
|
//| * AvgError - average error |
|
|
//| * AvgRelError - average relative error |
|
|
//| NOTE 1: RelCLSError/AvgCE are zero on regression problems. |
|
|
//| NOTE 2: on classification problems RMSError/AvgError/AvgRelError |
|
|
//| contain errors in prediction of posterior probabilities |
|
|
//+------------------------------------------------------------------+
|
|
struct CModelErrors
|
|
{
|
|
double m_RelCLSError;
|
|
double m_AvgCE;
|
|
double m_RMSError;
|
|
double m_AvgError;
|
|
double m_AvgRelError;
|
|
//---
|
|
CModelErrors(void) { ZeroMemory(this); }
|
|
~CModelErrors(void) {}
|
|
//---
|
|
void Copy(const CModelErrors &obj);
|
|
//--- overloading
|
|
void operator=(const CModelErrors &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CModelErrors::Copy(const CModelErrors &obj)
|
|
{
|
|
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;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//|This structure is used to store MLP error and gradient. |
|
|
//+------------------------------------------------------------------+
|
|
struct CSMLPGrad
|
|
{
|
|
double m_F;
|
|
CRowDouble m_G;
|
|
//---
|
|
CSMLPGrad(void) { m_F=0; }
|
|
~CSMLPGrad(void) {}
|
|
//---
|
|
void Copy(const CSMLPGrad &obj) { m_F=obj.m_F; m_G=obj.m_G; }
|
|
//--- overloading
|
|
void operator=(const CSMLPGrad &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary class for CMLPBase |
|
|
//+------------------------------------------------------------------+
|
|
class CMultilayerPerceptron
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_hlnetworktype;
|
|
int m_hlnormtype;
|
|
CModelErrors m_err;
|
|
CSMLPGrad m_grad;
|
|
//--- arrays
|
|
CRowInt m_dummyidx;
|
|
CRowInt m_hllayersizes;
|
|
CRowInt m_hlconnections;
|
|
CRowInt m_hlneurons;
|
|
CRowInt m_integerbuf;
|
|
CRowInt m_structinfo;
|
|
CRowDouble m_weights;
|
|
CRowDouble m_columnmeans;
|
|
CRowDouble m_columnsigmas;
|
|
CRowDouble m_neurons;
|
|
CRowDouble m_dfdnet;
|
|
CRowDouble m_derror;
|
|
CRowDouble m_x;
|
|
CRowDouble m_y;
|
|
CRowDouble m_xyrow;
|
|
CRowDouble m_nwbuf;
|
|
CRowDouble m_rndbuf;
|
|
//--- matrix
|
|
CMatrixDouble m_xy;
|
|
CMatrixDouble m_dummydxy;
|
|
CSparseMatrix m_dummysxy;
|
|
//--- constructor, destructor
|
|
CMultilayerPerceptron(void) { m_hlnetworktype=0; m_hlnormtype=0; }
|
|
~CMultilayerPerceptron(void) {}
|
|
//--- copy
|
|
void Copy(const CMultilayerPerceptron &obj);
|
|
//--- overloading
|
|
void operator=(const CMultilayerPerceptron &obj) { Copy(obj); }
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMultilayerPerceptron::Copy(const CMultilayerPerceptron &obj)
|
|
{
|
|
//--- copy variables
|
|
m_hlnetworktype=obj.m_hlnetworktype;
|
|
m_hlnormtype=obj.m_hlnormtype;
|
|
m_err=obj.m_err;
|
|
m_grad=obj.m_grad;
|
|
//--- copy arrays
|
|
m_dummyidx=obj.m_dummyidx;
|
|
m_hllayersizes=obj.m_hllayersizes;
|
|
m_hlconnections=obj.m_hlconnections;
|
|
m_hlneurons=obj.m_hlneurons;
|
|
m_integerbuf=obj.m_integerbuf;
|
|
m_structinfo=obj.m_structinfo;
|
|
m_weights=obj.m_weights;
|
|
m_columnmeans=obj.m_columnmeans;
|
|
m_columnsigmas=obj.m_columnsigmas;
|
|
m_neurons=obj.m_neurons;
|
|
m_dfdnet=obj.m_dfdnet;
|
|
m_derror=obj.m_derror;
|
|
m_x=obj.m_x;
|
|
m_y=obj.m_y;
|
|
m_xyrow=obj.m_xyrow;
|
|
m_nwbuf=obj.m_nwbuf;
|
|
m_rndbuf=obj.m_rndbuf;
|
|
//--- copy matrix
|
|
m_xy=obj.m_xy;
|
|
m_dummydxy=obj.m_dummydxy;
|
|
m_dummysxy=obj.m_dummysxy;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This class is a shell for class CMultilayerPerceptron |
|
|
//+------------------------------------------------------------------+
|
|
class CMultilayerPerceptronShell
|
|
{
|
|
private:
|
|
CMultilayerPerceptron m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMultilayerPerceptronShell(void) {}
|
|
CMultilayerPerceptronShell(CMultilayerPerceptron &obj) { m_innerobj.Copy(obj); }
|
|
~CMultilayerPerceptronShell(void) {}
|
|
//--- method
|
|
CMultilayerPerceptron *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Multilayer perceptron class |
|
|
//+------------------------------------------------------------------+
|
|
class CMLPBase
|
|
{
|
|
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_gradbasecasecost;
|
|
static const int m_microbatchsize;
|
|
//--- public methods
|
|
static int MLPGradSplitCost(void);
|
|
static int MLPGradSplitSize(void);
|
|
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(const CMultilayerPerceptron &network1,CMultilayerPerceptron &network2);
|
|
static bool MLPSameArchitecture(CMultilayerPerceptron &network1,CMultilayerPerceptron &network2);
|
|
static void MLPCopyTunableParameters(CMultilayerPerceptron &network1,CMultilayerPerceptron &network2);
|
|
static void MLPExportTunableParameters(CMultilayerPerceptron &network,CRowDouble &p,int &pcount);
|
|
static void MLPImportTunableParameters(CMultilayerPerceptron &network,CRowDouble &p);
|
|
static void MLPSerializeOld(CMultilayerPerceptron &network,double &ra[],int &rlen);
|
|
static void MLPSerializeOld(CMultilayerPerceptron &network,CRowDouble &ra,int &rlen);
|
|
static void MLPUnserializeOld(double &ra[],CMultilayerPerceptron &network);
|
|
static void MLPUnserializeOld(CRowDouble &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 MLPInitPreprocessorSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int ssize);
|
|
static void MLPInitPreprocessorSubset(CMultilayerPerceptron &network,CMatrixDouble &xy,int setsize,CRowInt &idx,int subsetsize);
|
|
static void MLPInitPreprocessorSparseSubset(CMultilayerPerceptron &network,CSparseMatrix &xy,int setsize,CRowInt &idx,int subsetsize);
|
|
static void MLPProperties(CMultilayerPerceptron &network,int &nin,int &nout,int &wcount);
|
|
static int MLPNTotal(CMultilayerPerceptron &network);
|
|
static int MLPGetInputsCount(CMultilayerPerceptron &network);
|
|
static int MLPGetOutputsCount(CMultilayerPerceptron &network);
|
|
static int MLPGetWeightsCount(CMultilayerPerceptron &network);
|
|
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 MLPProcess(CMultilayerPerceptron &network,CRowDouble &x,CRowDouble &y);
|
|
static void MLPProcessI(CMultilayerPerceptron &network,double &x[],double &y[]);
|
|
static void MLPProcessI(CMultilayerPerceptron &network,CRowDouble &x,CRowDouble &y);
|
|
static double MLPError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
|
|
static double MLPErrorSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int npoints);
|
|
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 MLPRelClsErrorSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int npoints);
|
|
static double MLPAvgCE(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
|
|
static double MLPAvgCESparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int npoints);
|
|
static double MLPRMSError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
|
|
static double MLPRMSErrorSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int npoints);
|
|
static double MLPAvgError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
|
|
static double MLPAvgErrorSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int npoints);
|
|
static double MLPAvgRelError(CMultilayerPerceptron &network,CMatrixDouble &xy,const int npoints);
|
|
static double MLPAvgRelErrorSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int npoints);
|
|
static void MLPGrad(CMultilayerPerceptron &network,double &x[],double &desiredy[],double &e,double &grad[]);
|
|
static void MLPGrad(CMultilayerPerceptron &network,CRowDouble &x,CRowDouble &desiredy,double &e,CRowDouble &grad);
|
|
static void MLPGradN(CMultilayerPerceptron &network,double &x[],double &desiredy[],double &e,double &grad[]);
|
|
static void MLPGradN(CMultilayerPerceptron &network,CRowDouble &x,CRowDouble &desiredy,double &e,CRowDouble &grad);
|
|
static void MLPGradBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,double &grad[]);
|
|
static void MLPGradBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,CRowDouble &grad);
|
|
static void MLPGradBatchSparse(CMultilayerPerceptron &network,CSparseMatrix &xy,int ssize,double &e,CRowDouble &grad);
|
|
static void MLPGradBatchSubset(CMultilayerPerceptron &network,CMatrixDouble &xy,int setsize,CRowInt &idx,int subsetsize,double &e,CRowDouble &grad);
|
|
static void MLPGradBatchSparseSubset(CMultilayerPerceptron &network,CSparseMatrix &xy,int setsize,CRowInt &idx,int subsetsize,double &e,CRowDouble &grad);
|
|
static void MLPGradBatchX(CMultilayerPerceptron &network,CMatrixDouble &densexy,CSparseMatrix &sparsexy,int datasetsize,int datasettype,CRowInt &idx,int subset0,int subset1,int subsettype,double &e,CRowDouble &grad);
|
|
static void MLPGradNBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,double &grad[]);
|
|
static void MLPGradNBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,CRowDouble &grad);
|
|
static void MLPHessianNBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,double &grad[],CMatrixDouble &h);
|
|
static void MLPHessianNBatch(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,double &e,CRowDouble &grad,CMatrixDouble &h);
|
|
static void MLPHessianBatch(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,CRowDouble &grad,CMatrixDouble &h);
|
|
static void MLPInternalProcessVector(int &structinfo[],double &weights[],double &columnmeans[],double &columnsigmas[],double &neurons[],double &dfdnet[],double &x[],double &y[]);
|
|
static void MLPInternalProcessVector(CRowInt &structinfo,CRowDouble &weights,CRowDouble &columnmeans,CRowDouble &columnsigmas,CRowDouble &neurons,CRowDouble &dfdnet,CRowDouble &x,CRowDouble &y);
|
|
static void MLPAlloc(CSerializer &s,CMultilayerPerceptron &network);
|
|
static void MLPSerialize(CSerializer &s,CMultilayerPerceptron &network);
|
|
static void MLPUnserialize(CSerializer &s,CMultilayerPerceptron &network);
|
|
static void MLPAllErrorsSubset(CMultilayerPerceptron &network,CMatrixDouble &xy,int setsize,CRowInt &subset,int subsetsize,CModelErrors &rep);
|
|
static void MLPAllErrorsSparseSubset(CMultilayerPerceptron &network,CSparseMatrix &xy,int setsize,CRowInt &subset,int subsetsize,CModelErrors &rep);
|
|
static double MLPErrorSubset(CMultilayerPerceptron &network,CMatrixDouble &xy,int setsize,CRowInt &subset,int subsetsize);
|
|
static double MLPErrorSparseSubset(CMultilayerPerceptron &network,CSparseMatrix &xy,int setsize,CRowInt &subset,int subsetsize);
|
|
static void MLPAllErrorsX(CMultilayerPerceptron &network,CMatrixDouble &densexy,CSparseMatrix &sparsexy,int datasetsize,int datasettype,CRowInt &idx,int subset0,int subset1,int subsettype,CModelErrors &rep);
|
|
|
|
private:
|
|
static void AddInputLayer(const int ncount,CRowInt &lsizes,CRowInt <ypes,CRowInt &lconnfirst,CRowInt &lconnlast,int &lastproc);
|
|
static void AddBiasedSummatorLayer(const int ncount,CRowInt &lsizes,CRowInt <ypes,CRowInt &lconnfirst,CRowInt &lconnlast,int &lastproc);
|
|
static void AddActivationLayer(const int functype,CRowInt &lsizes,CRowInt <ypes,CRowInt &lconnfirst,CRowInt &lconnlast,int &lastproc);
|
|
static void AddZeroLayer(CRowInt &lsizes,CRowInt <ypes,CRowInt &lconnfirst,CRowInt &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,CRowInt &lsizes,CRowInt <ypes,CRowInt &lconnfirst,CRowInt &lconnlast,const int layerscount,const bool isclsnet,CMultilayerPerceptron &network);
|
|
static void MLPHessianBatchInternal(CMultilayerPerceptron &network,CMatrixDouble &xy,const int ssize,const bool naturalerr,double &e,CRowDouble &grad,CMatrixDouble &h);
|
|
static void MLPInternalCalculateGradient(CMultilayerPerceptron &network,CRowDouble &neurons,CRowDouble &weights,CRowDouble &derror,CRowDouble &grad,const bool naturalerrorfunc);
|
|
static void MLPChunkedGradient(CMultilayerPerceptron &network,CMatrixDouble &xy,const int cstart,const int csize,CRowDouble &batch4buf,CRowDouble &hpcbuf,double &e,const bool naturalerrorfunc);
|
|
static void MLPChunkedProcess(CMultilayerPerceptron &network,CMatrixDouble &xy,int cstart,int csize,CRowDouble &batch4buf,CRowDouble &hpcbuf);
|
|
static double SafeCrossEntropy(const double t,const double z);
|
|
static void RandomizeBackwardPass(CMultilayerPerceptron &network,int neuronidx,double v);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| 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_gradbasecasecost=50000;
|
|
const int CMLPBase::m_microbatchsize=64;
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns number of weights updates which is required|
|
|
//| for gradient calculation problem to be splitted. |
|
|
//+------------------------------------------------------------------+
|
|
int CMLPBase::MLPGradSplitCost(void)
|
|
{
|
|
return(m_gradbasecasecost);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns number of elements in subset of dataset |
|
|
//| which is required for gradient calculation problem to be splitted|
|
|
//+------------------------------------------------------------------+
|
|
int CMLPBase::MLPGradSplitSize(void)
|
|
{
|
|
return(m_microbatchsize);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Creates neural network with NIn inputs, NOut outputs, |
|
|
//| without hidden layers, with linear output layer. Network weights |
|
|
//| are filled with small random values. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPCreate0(const int nin,const int nout,
|
|
CMultilayerPerceptron &network)
|
|
{
|
|
//--- create variables
|
|
int layerscount=0;
|
|
int lastproc=0;
|
|
//--- creating arrays
|
|
CRowInt lsizes;
|
|
CRowInt ltypes;
|
|
CRowInt lconnfirst;
|
|
CRowInt lconnlast;
|
|
//--- initialization
|
|
layerscount=4;
|
|
//--- Allocate arrays
|
|
lsizes.Resize(layerscount);
|
|
ltypes.Resize(layerscount);
|
|
lconnfirst.Resize(layerscount);
|
|
lconnlast.Resize(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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPCreate1(const int nin,const int nhid,const int nout,
|
|
CMultilayerPerceptron &network)
|
|
{
|
|
//--- create variables
|
|
int layerscount=0;
|
|
int lastproc=0;
|
|
//--- creating arrays
|
|
CRowInt lsizes;
|
|
CRowInt ltypes;
|
|
CRowInt lconnfirst;
|
|
CRowInt lconnlast;
|
|
//--- create variables
|
|
layerscount=7;
|
|
//--- Allocate arrays
|
|
lsizes.Resize(layerscount);
|
|
ltypes.Resize(layerscount);
|
|
lconnfirst.Resize(layerscount);
|
|
lconnlast.Resize(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 |
|
|
//+------------------------------------------------------------------+
|
|
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
|
|
CRowInt lsizes;
|
|
CRowInt ltypes;
|
|
CRowInt lconnfirst;
|
|
CRowInt lconnlast;
|
|
//--- initialization
|
|
layerscount=10;
|
|
//--- Allocate arrays
|
|
lsizes.Resize(layerscount);
|
|
ltypes.Resize(layerscount);
|
|
lconnfirst.Resize(layerscount);
|
|
lconnlast.Resize(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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPCreateB0(const int nin,const int nout,const double b,
|
|
double d,CMultilayerPerceptron &network)
|
|
{
|
|
//--- create variables
|
|
int layerscount=0;
|
|
int lastproc=0;
|
|
//--- creating arrays
|
|
CRowInt lsizes;
|
|
CRowInt ltypes;
|
|
CRowInt lconnfirst;
|
|
CRowInt lconnlast;
|
|
//--- initialization
|
|
layerscount=4;
|
|
//--- check
|
|
if(d>=0.0)
|
|
d=1;
|
|
else
|
|
d=-1;
|
|
//--- Allocate arrays
|
|
lsizes.Resize(layerscount);
|
|
ltypes.Resize(layerscount);
|
|
lconnfirst.Resize(layerscount);
|
|
lconnlast.Resize(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(int i=nin; i<nin+nout; i++)
|
|
{
|
|
network.m_columnmeans.Set(i,b);
|
|
network.m_columnsigmas.Set(i,d);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Same as MLPCreateB0 but with non-linear hidden layer. |
|
|
//+------------------------------------------------------------------+
|
|
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;
|
|
//--- creating arrays
|
|
CRowInt lsizes;
|
|
CRowInt ltypes;
|
|
CRowInt lconnfirst;
|
|
CRowInt lconnlast;
|
|
layerscount=7;
|
|
//--- check
|
|
if(d>=0.0)
|
|
d=1;
|
|
else
|
|
d=-1;
|
|
//--- Allocate arrays
|
|
lsizes.Resize(layerscount);
|
|
ltypes.Resize(layerscount);
|
|
lconnfirst.Resize(layerscount);
|
|
lconnlast.Resize(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(int i=nin; i<nin+nout; i++)
|
|
{
|
|
network.m_columnmeans.Set(i,b);
|
|
network.m_columnsigmas.Set(i,d);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Same as MLPCreateB0 but with two non-linear hidden layers. |
|
|
//+------------------------------------------------------------------+
|
|
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;
|
|
//--- creating arrays
|
|
CRowInt lsizes;
|
|
CRowInt ltypes;
|
|
CRowInt lconnfirst;
|
|
CRowInt lconnlast;
|
|
//--- initialization
|
|
layerscount=10;
|
|
//--- check
|
|
if(d>=0.0)
|
|
d=1;
|
|
else
|
|
d=-1;
|
|
//--- Allocate arrays
|
|
lsizes.Resize(layerscount);
|
|
ltypes.Resize(layerscount);
|
|
lconnfirst.Resize(layerscount);
|
|
lconnlast.Resize(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(int i=nin; i<nin+nout; i++)
|
|
{
|
|
network.m_columnmeans.Set(i,b);
|
|
network.m_columnsigmas.Set(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]. |
|
|
//+------------------------------------------------------------------+
|
|
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;
|
|
//--- creating arrays
|
|
CRowInt lsizes;
|
|
CRowInt ltypes;
|
|
CRowInt lconnfirst;
|
|
CRowInt lconnlast;
|
|
//--- initialization
|
|
layerscount=4;
|
|
//--- Allocate arrays
|
|
lsizes.Resize(layerscount);
|
|
ltypes.Resize(layerscount);
|
|
lconnfirst.Resize(layerscount);
|
|
lconnlast.Resize(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(int i=nin; i<nin+nout; i++)
|
|
{
|
|
network.m_columnmeans.Set(i,0.5*(a+b));
|
|
network.m_columnsigmas.Set(i,0.5*(a-b));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Same as MLPCreateR0,but with non-linear hidden layer. |
|
|
//+------------------------------------------------------------------+
|
|
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;
|
|
//--- creating arrays
|
|
CRowInt lsizes;
|
|
CRowInt ltypes;
|
|
CRowInt lconnfirst;
|
|
CRowInt lconnlast;
|
|
//--- initialization
|
|
layerscount=7;
|
|
//--- Allocate arrays
|
|
lsizes.Resize(layerscount);
|
|
ltypes.Resize(layerscount);
|
|
lconnfirst.Resize(layerscount);
|
|
lconnlast.Resize(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(int i=nin; i<nin+nout; i++)
|
|
{
|
|
network.m_columnmeans.Set(i,0.5*(a+b));
|
|
network.m_columnsigmas.Set(i,0.5*(a-b));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Same as MLPCreateR0,but with two non-linear hidden layers. |
|
|
//+------------------------------------------------------------------+
|
|
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;
|
|
//--- creating arrays
|
|
CRowInt lsizes;
|
|
CRowInt ltypes;
|
|
CRowInt lconnfirst;
|
|
CRowInt lconnlast;
|
|
//--- initialization
|
|
layerscount=10;
|
|
//--- Allocate arrays
|
|
lsizes.Resize(layerscount);
|
|
ltypes.Resize(layerscount);
|
|
lconnfirst.Resize(layerscount);
|
|
lconnlast.Resize(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(int i=nin; i<nin+nout; i++)
|
|
{
|
|
network.m_columnmeans.Set(i,0.5*(a+b));
|
|
network.m_columnsigmas.Set(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). |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPCreateC0(const int nin,const int nout,
|
|
CMultilayerPerceptron &network)
|
|
{
|
|
//--- create variables
|
|
int layerscount=0;
|
|
int lastproc=0;
|
|
//--- creating arrays
|
|
CRowInt lsizes;
|
|
CRowInt ltypes;
|
|
CRowInt lconnfirst;
|
|
CRowInt lconnlast;
|
|
//--- check
|
|
if(!CAp::Assert(nout>=2,__FUNCTION__+": NOut<2!"))
|
|
return;
|
|
//--- initialization
|
|
layerscount=4;
|
|
//--- Allocate arrays
|
|
lsizes.Resize(layerscount);
|
|
ltypes.Resize(layerscount);
|
|
lconnfirst.Resize(layerscount);
|
|
lconnlast.Resize(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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPCreateC1(const int nin,const int nhid,const int nout,
|
|
CMultilayerPerceptron &network)
|
|
{
|
|
//--- create variables
|
|
int layerscount=0;
|
|
int lastproc=0;
|
|
//--- creating arrays
|
|
CRowInt lsizes;
|
|
CRowInt ltypes;
|
|
CRowInt lconnfirst;
|
|
CRowInt lconnlast;
|
|
//--- check
|
|
if(!CAp::Assert(nout>=2,__FUNCTION__+": NOut<2!"))
|
|
return;
|
|
//--- initialization
|
|
layerscount=7;
|
|
//--- Allocate arrays
|
|
lsizes.Resize(layerscount);
|
|
ltypes.Resize(layerscount);
|
|
lconnfirst.Resize(layerscount);
|
|
lconnlast.Resize(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. |
|
|
//+------------------------------------------------------------------+
|
|
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
|
|
CRowInt lsizes;
|
|
CRowInt ltypes;
|
|
CRowInt lconnfirst;
|
|
CRowInt lconnlast;
|
|
//--- check
|
|
if(!CAp::Assert(nout>=2,__FUNCTION__+": NOut<2!"))
|
|
return;
|
|
//--- initialization
|
|
layerscount=10;
|
|
//--- Allocate arrays
|
|
lsizes.Resize(layerscount);
|
|
ltypes.Resize(layerscount);
|
|
lconnfirst.Resize(layerscount);
|
|
lconnlast.Resize(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 |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPCopy(const CMultilayerPerceptron &network1,
|
|
CMultilayerPerceptron &network2)
|
|
{
|
|
//--- copy
|
|
network2=network1;
|
|
network2.m_grad.m_F=0;
|
|
network2.m_grad.m_G.Fill(0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function compares architectures of neural networks. Only |
|
|
//| geometries are compared, weights and other parameters are |
|
|
//| not tested. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMLPBase::MLPSameArchitecture(CMultilayerPerceptron &network1,
|
|
CMultilayerPerceptron &network2)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(network1.m_structinfo.Size()>0 && network1.m_structinfo.Size()>=network1.m_structinfo[0],__FUNCTION__": Network1 is uninitialized"))
|
|
return(false);
|
|
if(!CAp::Assert(network2.m_structinfo.Size()>0 && network2.m_structinfo.Size()>=network2.m_structinfo[0],__FUNCTION__": Network2 is uninitialized"))
|
|
return(false);
|
|
if(network1.m_structinfo[0]!=network2.m_structinfo[0])
|
|
return(false);
|
|
|
|
int ninfo=network1.m_structinfo[0];
|
|
for(int i=0; i<ninfo; i++)
|
|
if(network1.m_structinfo[i]!=network2.m_structinfo[i])
|
|
return(false);
|
|
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function copies tunable parameters (weights/means/sigmas) |
|
|
//| from one network to another with same architecture. It performs |
|
|
//| some rudimentary checks that architectures are same, and throws |
|
|
//| exception if check fails. |
|
|
//| It is intended for fast copying of states between two network |
|
|
//| which are known to have same geometry. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network1 - source, must be correctly initialized |
|
|
//| Network2 - target, must have same architecture |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Network2 - network state is copied from source to target |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPCopyTunableParameters(CMultilayerPerceptron &network1,
|
|
CMultilayerPerceptron &network2)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
//--- check
|
|
if(!CAp::Assert(network1.m_structinfo.Size()>0 && network1.m_structinfo.Size()>=network1.m_structinfo[0],__FUNCTION__": Network1 is uninitialized"))
|
|
return;
|
|
if(!CAp::Assert(network2.m_structinfo.Size()>0 && network2.m_structinfo.Size()>=network2.m_structinfo[0],__FUNCTION__": Network2 is uninitialized"))
|
|
return;
|
|
if(!CAp::Assert(network1.m_structinfo[0]==network2.m_structinfo[0],__FUNCTION__": Network1 geometry differs from that of Network2"))
|
|
return;
|
|
|
|
int ninfo=network1.m_structinfo[0];
|
|
for(int i=0; i<ninfo; i++)
|
|
if(!CAp::Assert(network1.m_structinfo[i]==network2.m_structinfo[i],__FUNCTION__": Network1 geometry differs from that of Network2"))
|
|
return;
|
|
|
|
MLPProperties(network1,nin,nout,wcount);
|
|
network2.m_weights=network1.m_weights;
|
|
if(MLPIsSoftMax(network1))
|
|
{
|
|
network2.m_columnmeans=network1.m_columnmeans;
|
|
network2.m_columnsigmas=network1.m_columnsigmas;
|
|
}
|
|
else
|
|
{
|
|
network2.m_columnmeans=network1.m_columnmeans;
|
|
network2.m_columnsigmas=network1.m_columnsigmas;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function exports tunable parameters (weights/means/sigmas) |
|
|
//| from network to contiguous array. Nothing is guaranteed about |
|
|
//| array format, the only thing you can count for is that |
|
|
//| MLPImportTunableParameters() will be able to parse it. |
|
|
//| It is intended for fast copying of states between network and |
|
|
//| backup array |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - source, must be correctly initialized |
|
|
//| P - array to use. If its size is enough to store |
|
|
//| data, it is reused. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| P - array which stores network parameters, resized |
|
|
//| if needed |
|
|
//| PCount - number of parameters stored in array. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPExportTunableParameters(CMultilayerPerceptron &network,
|
|
CRowDouble &p,int &pcount)
|
|
{
|
|
//--- create variables
|
|
int k=0;
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
pcount=0;
|
|
//--- check
|
|
if(!CAp::Assert(network.m_structinfo.Size()>0 && network.m_structinfo.Size()>=network.m_structinfo[0],__FUNCTION__": Network is uninitialized"))
|
|
return;
|
|
MLPProperties(network,nin,nout,wcount);
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
pcount=wcount+2*nin;
|
|
CApServ::RVectorSetLengthAtLeast(p,pcount);
|
|
for(int i=0; i<wcount; i++)
|
|
p.Set(i,network.m_weights[i]);
|
|
k=wcount;
|
|
for(int i=0; i<nin; i++)
|
|
{
|
|
p.Set(k,network.m_columnmeans[i]);
|
|
p.Set(k+1,network.m_columnsigmas[i]);
|
|
k+=2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pcount=wcount+2*(nin+nout);
|
|
CApServ::RVectorSetLengthAtLeast(p,pcount);
|
|
for(int i=0; i<wcount; i++)
|
|
p.Set(i,network.m_weights[i]);
|
|
k=wcount;
|
|
for(int i=0; i<nin+nout; i++)
|
|
{
|
|
p.Set(k,network.m_columnmeans[i]);
|
|
p.Set(k+1,network.m_columnsigmas[i]);
|
|
k+=2;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function imports tunable parameters (weights/means/sigmas) |
|
|
//| which were exported by MLPExportTunableParameters(). |
|
|
//| It is intended for fast copying of states between network and |
|
|
//| backup array |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - target: |
|
|
//| * must be correctly initialized |
|
|
//| * must have same geometry as network used |
|
|
//| to export params |
|
|
//| P - array with parameters |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPImportTunableParameters(CMultilayerPerceptron &network,
|
|
CRowDouble &p)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int k=0;
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
//--- check
|
|
if(!CAp::Assert(network.m_structinfo.Size()>0 && network.m_structinfo.Size()>=network.m_structinfo[0],__FUNCTION__": Network is uninitialized"))
|
|
return;
|
|
MLPProperties(network,nin,nout,wcount);
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
for(i=0; i<wcount; i++)
|
|
network.m_weights.Set(i,p[i]);
|
|
k=wcount;
|
|
for(i=0; i<nin; i++)
|
|
{
|
|
network.m_columnmeans.Set(i,p[k]);
|
|
network.m_columnsigmas.Set(i,p[k+1]);
|
|
k+=2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for(i=0; i<wcount; i++)
|
|
network.m_weights.Set(i,p[i]);
|
|
k=wcount;
|
|
for(i=0; i<nin+nout; i++)
|
|
{
|
|
network.m_columnmeans.Set(i,p[k]);
|
|
network.m_columnsigmas.Set(i,p[k+1]);
|
|
k+=2;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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 |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPSerializeOld(CMultilayerPerceptron &network,
|
|
double &ra[],int &rlen)
|
|
{
|
|
CRowDouble RA;
|
|
MLPSerializeOld(network,ra,rlen);
|
|
RA.ToArray(ra);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPSerializeOld(CMultilayerPerceptron &network,
|
|
CRowDouble &ra,int &rlen)
|
|
{
|
|
//--- create variables
|
|
int ssize=network.m_structinfo[0];
|
|
int nin=network.m_structinfo[1];
|
|
int nout=network.m_structinfo[2];
|
|
int ntotal=network.m_structinfo[3];
|
|
int wcount=network.m_structinfo[4];
|
|
int sigmalen=0;
|
|
int offs=0;
|
|
int i=0;
|
|
int i_=0;
|
|
int i1_=0;
|
|
//--- 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
|
|
ra.Resize(rlen);
|
|
//--- change values
|
|
ra.Set(0,rlen);
|
|
ra.Set(1,m_mlpvnum);
|
|
ra.Set(2,ssize);
|
|
//--- calculation
|
|
offs=3;
|
|
for(i=0; i<ssize; i++)
|
|
ra.Set(offs+i,network.m_structinfo[i]);
|
|
//--- calculation
|
|
offs+=ssize;
|
|
for(i=0; i<wcount; i++)
|
|
ra.Set(offs+i,network.m_weights[i]);
|
|
//--- calculation
|
|
offs+=wcount;
|
|
for(i=0; i<sigmalen; i++)
|
|
ra.Set(offs+i,network.m_columnmeans[i]);
|
|
//--- calculation
|
|
offs+=sigmalen;
|
|
for(i=0; i<sigmalen; i++)
|
|
ra.Set(offs+i,network.m_columnsigmas[i]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Unserialization of MultiLayerPerceptron strucure |
|
|
//| INPUT PARAMETERS: |
|
|
//| RA - real array which stores network |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Network - restored network |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPUnserializeOld(double &ra[],CMultilayerPerceptron &network)
|
|
{
|
|
CRowDouble RA=ra;
|
|
MLPUnserializeOld(RA,network);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPUnserializeOld(CRowDouble &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
|
|
network.m_structinfo.Resize(ssize);
|
|
for(i=0; i<ssize; i++)
|
|
network.m_structinfo.Set(i,(int)MathRound(ra[offs+i]));
|
|
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
|
|
network.m_weights.Resize(wcount);
|
|
network.m_columnmeans.Resize(sigmalen);
|
|
network.m_columnsigmas.Resize(sigmalen);
|
|
network.m_neurons.Resize(ntotal);
|
|
network.m_nwbuf.Resize(MathMax(wcount,2*nout));
|
|
network.m_dfdnet.Resize(ntotal);
|
|
network.m_x.Resize(nin);
|
|
network.m_y.Resize(nout);
|
|
network.m_derror.Resize(ntotal);
|
|
//--- Copy parameters from RA
|
|
for(i=0; i<wcount; i++)
|
|
network.m_weights.Set(i_,ra[i+offs]);
|
|
//--- calculation
|
|
offs+=wcount;
|
|
for(i=0; i_<sigmalen; i++)
|
|
network.m_columnmeans.Set(i,ra[i+offs]);
|
|
//--- calculation
|
|
offs+=sigmalen;
|
|
for(i=0; i<sigmalen; i++)
|
|
network.m_columnsigmas.Set(i,ra[i+offs]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Randomization of neural network weights |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPRandomize(CMultilayerPerceptron &network)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int ntotal=0;
|
|
int istart=0;
|
|
CHighQualityRandState r;
|
|
int entrysize=0;
|
|
int entryoffs=0;
|
|
int neuronidx=0;
|
|
int neurontype=0;
|
|
double vmean=0;
|
|
double vvar=0;
|
|
int i=0;
|
|
int n1=0;
|
|
int n2=0;
|
|
double desiredsigma=0;
|
|
int montecarlocnt=0;
|
|
double ef=0;
|
|
double ef2=0;
|
|
double v=0;
|
|
double wscale=0;
|
|
CHighQualityRand::HQRndRandomize(r);
|
|
|
|
MLPProperties(network,nin,nout,wcount);
|
|
ntotal=network.m_structinfo[3];
|
|
istart=network.m_structinfo[5];
|
|
desiredsigma=0.5;
|
|
montecarlocnt=20;
|
|
//--- Stage 1:
|
|
//--- * Network.Weights is filled by standard deviation of weights
|
|
//--- * default values: sigma=1
|
|
network.m_weights.Fill(1.0);
|
|
//--- Stage 2:
|
|
//--- * assume that input neurons have zero mean and unit standard deviation
|
|
//--- * assume that constant neurons have zero standard deviation
|
|
//--- * perform forward pass along neurons
|
|
//--- * for each non-input non-constant neuron:
|
|
//--- * calculate mean and standard deviation of neuron's output
|
|
//--- assuming that we know means/deviations of neurons which feed it
|
|
//--- and assuming that weights has unit variance and zero mean.
|
|
//--- * for each nonlinear neuron additionally we perform backward pass:
|
|
//--- * scale variances of weights which feed it in such way that neuron's
|
|
//--- input has unit standard deviation
|
|
//--- NOTE: this algorithm assumes that each connection feeds at most one
|
|
//--- non-linear neuron. This assumption can be incorrect in upcoming
|
|
//--- architectures with strong neurons. However, algorithm should
|
|
//--- work smoothly even in this case.
|
|
//--- During this stage we use Network.RndBuf, which is grouped into NTotal
|
|
//--- entries, each of them having following format:
|
|
//--- Buf[Offset+0] mean value of neuron's output
|
|
//--- Buf[Offset+1] standard deviation of neuron's output
|
|
entrysize=2;
|
|
CApServ::RVectorSetLengthAtLeast(network.m_rndbuf,entrysize*ntotal);
|
|
for(neuronidx=0; neuronidx<ntotal; neuronidx++)
|
|
{
|
|
neurontype=network.m_structinfo[istart+neuronidx*m_nfieldwidth];
|
|
entryoffs=entrysize*neuronidx;
|
|
switch(neurontype)
|
|
{
|
|
case -2:
|
|
//--- Input neuron: zero mean, unit variance.
|
|
network.m_rndbuf.Set(entryoffs,0.0);
|
|
network.m_rndbuf.Set(entryoffs+1,1.0);
|
|
break;
|
|
case -3:
|
|
//--- "-1" neuron: mean=-1, zero variance.
|
|
network.m_rndbuf.Set(entryoffs,-1.0);
|
|
network.m_rndbuf.Set(entryoffs+1,0.0);
|
|
break;
|
|
case -4:
|
|
//--- "0" neuron: mean=0, zero variance.
|
|
network.m_rndbuf.Set(entryoffs,0.0);
|
|
network.m_rndbuf.Set(entryoffs+1,0.0);
|
|
break;
|
|
case 0:
|
|
//--- Adaptive summator neuron:
|
|
//--- * calculate its mean and variance.
|
|
//--- * we assume that weights of this neuron have unit variance and zero mean.
|
|
//--- * thus, neuron's output is always have zero mean
|
|
//--- * as for variance, it is a bit more interesting:
|
|
//--- * let n[i] is i-th input neuron
|
|
//--- * let w[i] is i-th weight
|
|
//--- * we assume that n[i] and w[i] are independently distributed
|
|
//--- * Var(n0*w0+n1*w1+...) = Var(n0*w0)+Var(n1*w1)+...
|
|
//--- * Var(X*Y) = mean(X)^2*Var(Y) + mean(Y)^2*Var(X) + Var(X)*Var(Y)
|
|
//--- * mean(w[i])=0, var(w[i])=1
|
|
//--- * Var(n[i]*w[i]) = mean(n[i])^2 + Var(n[i])
|
|
n1=network.m_structinfo[istart+neuronidx*m_nfieldwidth+2];
|
|
n2=n1+network.m_structinfo[istart+neuronidx*m_nfieldwidth+1];
|
|
vmean=0.0;
|
|
vvar=0.0;
|
|
for(i=n1; i<n2; i++)
|
|
vvar+=CMath::Sqr(network.m_rndbuf[entrysize*i])+CMath::Sqr(network.m_rndbuf[entrysize*i+1]);
|
|
network.m_rndbuf.Set(entryoffs,vmean);
|
|
network.m_rndbuf.Set(entryoffs+1,MathSqrt(vvar));
|
|
break;
|
|
case -5:
|
|
//--- Linear activation function
|
|
i=network.m_structinfo[istart+neuronidx*m_nfieldwidth+2];
|
|
vmean=network.m_rndbuf[entrysize*i];
|
|
vvar=CMath::Sqr(network.m_rndbuf[entrysize*i+1]);
|
|
if(vvar>0.0)
|
|
wscale=desiredsigma/MathSqrt(vvar);
|
|
else
|
|
wscale=1.0;
|
|
RandomizeBackwardPass(network,i,wscale);
|
|
network.m_rndbuf.Set(entryoffs,vmean*wscale);
|
|
network.m_rndbuf.Set(entryoffs+1,desiredsigma);
|
|
break;
|
|
default:
|
|
if(neurontype>0)
|
|
{
|
|
//--- Nonlinear activation function:
|
|
//--- * scale its inputs
|
|
//--- * estimate mean/sigma of its output using Monte-Carlo method
|
|
//--- (we simulate different inputs with unit deviation and
|
|
//--- sample activation function output on such inputs)
|
|
i=network.m_structinfo[istart+neuronidx*m_nfieldwidth+2];
|
|
vmean=network.m_rndbuf[entrysize*i];
|
|
vvar=CMath::Sqr(network.m_rndbuf[entrysize*i+1]);
|
|
if(vvar>0.0)
|
|
wscale=desiredsigma/MathSqrt(vvar);
|
|
else
|
|
wscale=1.0;
|
|
RandomizeBackwardPass(network,i,wscale);
|
|
ef=0.0;
|
|
ef2=0.0;
|
|
vmean=vmean*wscale;
|
|
for(i=0; i<montecarlocnt; i++)
|
|
{
|
|
v=vmean+desiredsigma*CHighQualityRand::HQRndNormal(r);
|
|
ef+=v;
|
|
ef2+=v*v;
|
|
}
|
|
ef=ef/montecarlocnt;
|
|
ef2=ef2/montecarlocnt;
|
|
network.m_rndbuf.Set(entryoffs,ef);
|
|
network.m_rndbuf.Set(entryoffs+1,MathMax(ef2-ef*ef,0.0));
|
|
}
|
|
else
|
|
{
|
|
CAp::Assert(false,__FUNCTION__": unexpected neuron type");
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
//--- Stage 3: generate weights.
|
|
for(i=0; i<wcount; i++)
|
|
network.m_weights.Mul(i,CHighQualityRand::HQRndNormal(r));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Randomization of neural network weights and standartisator |
|
|
//+------------------------------------------------------------------+
|
|
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
|
|
MLPRandomize(network);
|
|
for(i=0; i<nin; i++)
|
|
{
|
|
network.m_columnmeans.Set(i,CMath::RandomReal()-0.5);
|
|
network.m_columnsigmas.Set(i,CMath::RandomReal()+0.5);
|
|
}
|
|
//--- check
|
|
if(!MLPIsSoftMax(network))
|
|
{
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
offs=istart+(ntotal-nout+i)*m_nfieldwidth;
|
|
ntype=network.m_structinfo[offs];
|
|
//--- check
|
|
if(ntype==0)
|
|
{
|
|
//--- Shifts are changed only for linear outputs neurons
|
|
network.m_columnmeans.Set(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.Set(nin+i,MathSign(network.m_columnsigmas[nin+i])*(1.5*CMath::RandomReal()+0.5));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPInitPreprocessor(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int ssize)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int ntotal=0;
|
|
int istart=0;
|
|
int offs=0;
|
|
int ntype=0;
|
|
int jmax=0;
|
|
double s=0;
|
|
//--- creating arrays
|
|
vector<double> means;
|
|
vector<double> sigmas;
|
|
//--- function call
|
|
MLPProperties(network,nin,nout,wcount);
|
|
//--- initialization
|
|
ntotal=network.m_structinfo[3];
|
|
istart=network.m_structinfo[5];
|
|
//--- allocation
|
|
if(ssize>0)
|
|
{
|
|
matrix<double> XY=xy.ToMatrix();
|
|
XY.Resize(ssize,nin+nout);
|
|
means=XY.Mean(0);
|
|
sigmas=XY.Std(0);
|
|
}
|
|
else
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
jmax=nin+1;
|
|
else
|
|
jmax=nin+nout;
|
|
means=vector<double>::Zeros(jmax);
|
|
sigmas=vector<double>::Ones(jmax);
|
|
}
|
|
//--- Inputs
|
|
for(i=0; i<nin; i++)
|
|
{
|
|
network.m_columnmeans.Set(i,means[i]);
|
|
network.m_columnsigmas.Set(i,(sigmas[i]!=0?sigmas[i]:1));
|
|
}
|
|
//--- Outputs
|
|
if(!MLPIsSoftMax(network))
|
|
{
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
offs=istart+(ntotal-nout+i)*m_nfieldwidth;
|
|
ntype=network.m_structinfo[offs];
|
|
//--- Linear outputs
|
|
if(ntype==0)
|
|
{
|
|
network.m_columnmeans.Set(nin+i,means[nin+i]);
|
|
network.m_columnsigmas.Set(nin+i,(sigmas[nin+i]!=0?sigmas[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.Set(nin+i,MathSign(network.m_columnsigmas[nin+i])*MathAbs(s));
|
|
//--- check
|
|
if(network.m_columnsigmas[nin+i]==0.0)
|
|
network.m_columnsigmas.Set(nin+i,1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine. |
|
|
//| Initialization for preprocessor based on a sample. |
|
|
//| INPUT |
|
|
//| Network - initialized neural network; |
|
|
//| XY - sample, given by sparse matrix; |
|
|
//| SSize - sample size. |
|
|
//| OUTPUT |
|
|
//| Network - neural network with initialised preprocessor. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPInitPreprocessorSparse(CMultilayerPerceptron &network,
|
|
CSparseMatrix &xy,int ssize)
|
|
{
|
|
//--- create variables
|
|
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;
|
|
int i=0;
|
|
int j=0;
|
|
//--- create arrays
|
|
vector<double> means;
|
|
vector<double> sigmas;
|
|
CMatrixDouble dense_xy;
|
|
|
|
MLPProperties(network,nin,nout,wcount);
|
|
ntotal=network.m_structinfo[3];
|
|
istart=network.m_structinfo[5];
|
|
//--- Means/Sigmas
|
|
if(MLPIsSoftMax(network))
|
|
jmax=nin+1;
|
|
else
|
|
jmax=nin+nout;
|
|
dense_xy=matrix<double>::Zeros(ssize,jmax);
|
|
for(i=0; i<ssize; i++)
|
|
{
|
|
CSparse::SparseGetRow(xy,i,network.m_xyrow);
|
|
dense_xy.Row(i,network.m_xyrow);
|
|
}
|
|
if(ssize>0)
|
|
{
|
|
means=dense_xy.Mean(0);
|
|
sigmas=dense_xy.Std(0);
|
|
}
|
|
else
|
|
{
|
|
means=vector<double>::Zeros(jmax);
|
|
sigmas=vector<double>::Ones(jmax);
|
|
}
|
|
//--- Inputs
|
|
for(i=0; i<nin; i++)
|
|
{
|
|
network.m_columnmeans.Set(i,means[i]);
|
|
network.m_columnsigmas.Set(i,(sigmas[i]!=0?sigmas[i]:1));
|
|
}
|
|
//--- Outputs
|
|
if(!MLPIsSoftMax(network))
|
|
{
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
offs=istart+(ntotal-nout+i)*m_nfieldwidth;
|
|
ntype=network.m_structinfo[offs];
|
|
//--- Linear outputs
|
|
if(ntype==0)
|
|
{
|
|
network.m_columnmeans.Set(nin+i,means[nin+i]);
|
|
network.m_columnsigmas.Set(nin+i,(sigmas[nin+i]!=0.0?sigmas[nin+i]:1.0));
|
|
}
|
|
//--- Bounded outputs (half-interval)
|
|
if(ntype==3)
|
|
{
|
|
s=means[nin+i]-network.m_columnmeans[nin+i];
|
|
if(s==0.0)
|
|
s=MathSign(network.m_columnsigmas[nin+i]);
|
|
if(s==0.0)
|
|
s=1.0;
|
|
s=MathSign(network.m_columnsigmas[nin+i])*MathAbs(s);
|
|
network.m_columnsigmas.Set(nin+i,(s!=0.0?s:1.0));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine. |
|
|
//| Initialization for preprocessor based on a subsample. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - network initialized with one of the network |
|
|
//| creation funcs |
|
|
//| XY - original dataset; one sample = one row; |
|
|
//| first NIn columns contain inputs, |
|
|
//| next NOut columns - desired outputs. |
|
|
//| SetSize - real size of XY, SetSize>=0; |
|
|
//| Idx - subset of SubsetSize elements, array[SubsetSize]|
|
|
//| * Idx[I] stores row index in the original dataset which is |
|
|
//| given by XY. Gradient is calculated with respect to rows |
|
|
//| whose indexes are stored in Idx[]. |
|
|
//| * Idx[] must store correct indexes; this function throws |
|
|
//| an exception in case incorrect index (less than 0 or |
|
|
//| larger than rows(XY)) is given |
|
|
//| * Idx[] may store indexes in any order and even with |
|
|
//| repetitions. |
|
|
//| SubsetSize - number of elements in Idx[] array. |
|
|
//| OUTPUT: |
|
|
//| Network - neural network with initialised preprocessor. |
|
|
//| NOTE: when SubsetSize<0 is used full dataset by call |
|
|
//| MLPInitPreprocessor function. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPInitPreprocessorSubset(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,int setsize,
|
|
CRowInt &idx,int subsetsize)
|
|
{
|
|
//--- create variables
|
|
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;
|
|
int npoints=0;
|
|
int i=0;
|
|
int j=0;
|
|
//--- create arrays
|
|
vector<double> means;
|
|
vector<double> sigmas;
|
|
//--- check
|
|
if(!CAp::Assert(setsize>=0,__FUNCTION__": SetSize<0"))
|
|
return;
|
|
if(subsetsize<0)
|
|
{
|
|
MLPInitPreprocessor(network,xy,setsize);
|
|
return;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(subsetsize<=idx.Size(),__FUNCTION__": SubsetSize>Length(Idx)"))
|
|
return;
|
|
npoints=setsize;
|
|
for(i=0; i<subsetsize; i++)
|
|
{
|
|
if(!CAp::Assert(idx[i]>=0,__FUNCTION__": incorrect index of XY row(Idx[I]<0)"))
|
|
return;
|
|
if(!CAp::Assert(idx[i]<npoints,__FUNCTION__": incorrect index of XY row(Idx[I]>=Rows(XY))"))
|
|
return;
|
|
}
|
|
//--- get Network info
|
|
MLPProperties(network,nin,nout,wcount);
|
|
ntotal=network.m_structinfo[3];
|
|
istart=network.m_structinfo[5];
|
|
//--- Means/Sigmas
|
|
if(MLPIsSoftMax(network))
|
|
jmax=nin;
|
|
else
|
|
jmax=nin+nout;
|
|
means=vector<double>::Zeros(jmax);
|
|
sigmas=means;
|
|
for(i=0; i<subsetsize; i++)
|
|
{
|
|
for(j=0; j<jmax; j++)
|
|
means[j]+=xy.Get(idx[i],j);
|
|
}
|
|
means=means/subsetsize;
|
|
for(i=0; i<subsetsize; i++)
|
|
{
|
|
for(j=0; j<jmax; j++)
|
|
sigmas[j]+=CMath::Sqr(xy.Get(idx[i],j)-means[j]);
|
|
}
|
|
sigmas=MathSqrt(sigmas/subsetsize);
|
|
//--- Inputs
|
|
for(i=0; i<nin; i++)
|
|
{
|
|
network.m_columnmeans.Set(i,means[i]);
|
|
network.m_columnsigmas.Set(i,(sigmas[i]!=0?sigmas[i]:1));
|
|
}
|
|
//--- Outputs
|
|
if(!MLPIsSoftMax(network))
|
|
{
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
offs=istart+(ntotal-nout+i)*m_nfieldwidth;
|
|
ntype=network.m_structinfo[offs];
|
|
//--- Linear outputs
|
|
if(ntype==0)
|
|
{
|
|
network.m_columnmeans.Set(nin+i,means[nin+i]);
|
|
network.m_columnsigmas.Set(nin+i,(sigmas[nin+i]!=0.0?sigmas[nin+i]:1.0));
|
|
}
|
|
//--- Bounded outputs (half-interval)
|
|
if(ntype==3)
|
|
{
|
|
s=means[nin+i]-network.m_columnmeans[nin+i];
|
|
if(s==0.0)
|
|
s=MathSign(network.m_columnsigmas[nin+i]);
|
|
if(s==0.0)
|
|
s=1.0;
|
|
s=MathSign(network.m_columnsigmas[nin+i])*MathAbs(s);
|
|
network.m_columnsigmas.Set(nin+i,(s!=0.0?s:1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine. |
|
|
//| Initialization for preprocessor based on a subsample. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - network initialized with one of the network |
|
|
//| creation funcs |
|
|
//| XY - original dataset, given by sparse matrix; |
|
|
//| one sample = one row; |
|
|
//| first NIn columns contain inputs, |
|
|
//| next NOut columns - desired outputs. |
|
|
//| SetSize - real size of XY, SetSize>=0; |
|
|
//| Idx - subset of SubsetSize elements, array[SubsetSize]: |
|
|
//| * Idx[I] stores row index in the original dataset which is |
|
|
//| given by XY. Gradient is calculated with respect to rows |
|
|
//| whose indexes are stored in Idx[]. |
|
|
//| * Idx[] must store correct indexes; this function throws |
|
|
//| an exception in case incorrect index (less than 0 or |
|
|
//| larger than rows(XY)) is given |
|
|
//| * Idx[] may store indexes in any order and even with |
|
|
//| repetitions. |
|
|
//| SubsetSize- number of elements in Idx[] array. |
|
|
//| OUTPUT: |
|
|
//| Network - neural network with initialised preprocessor. |
|
|
//| NOTE: when SubsetSize<0 is used full dataset by call |
|
|
//| MLPInitPreprocessorSparse function. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPInitPreprocessorSparseSubset(CMultilayerPerceptron &network,
|
|
CSparseMatrix &xy,int setsize,
|
|
CRowInt &idx,int subsetsize)
|
|
{
|
|
//--- create variables
|
|
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;
|
|
int npoints=0;
|
|
int i=0;
|
|
int j=0;
|
|
//--- create arrays
|
|
vector<double> means;
|
|
vector<double> sigmas;
|
|
//--- check
|
|
if(!CAp::Assert(setsize>=0,__FUNCTION__": SetSize<0"))
|
|
return;
|
|
if(subsetsize<0)
|
|
{
|
|
MLPInitPreprocessorSparse(network,xy,setsize);
|
|
return;
|
|
}
|
|
if(!CAp::Assert(subsetsize<=idx.Size(),__FUNCTION__": SubsetSize>Length(Idx)"))
|
|
return;
|
|
npoints=setsize;
|
|
for(i=0; i<subsetsize; i++)
|
|
{
|
|
if(!CAp::Assert(idx[i]>=0,__FUNCTION__": incorrect index of XY row(Idx[I]<0)"))
|
|
return;
|
|
if(!CAp::Assert(idx[i]<npoints,__FUNCTION__": incorrect index of XY row(Idx[I]>=Rows(XY))"))
|
|
return;
|
|
}
|
|
//--- get Network info
|
|
MLPProperties(network,nin,nout,wcount);
|
|
ntotal=network.m_structinfo[3];
|
|
istart=network.m_structinfo[5];
|
|
//
|
|
//--- Means/Sigmas
|
|
//
|
|
if(MLPIsSoftMax(network))
|
|
jmax=nin;
|
|
else
|
|
jmax=nin+nout;
|
|
means=vector<double>::Zeros(jmax);
|
|
sigmas=means;
|
|
for(i=0; i<subsetsize; i++)
|
|
{
|
|
CSparse::SparseGetRow(xy,idx[i],network.m_xyrow);
|
|
means+=network.m_xyrow.ToVector();
|
|
}
|
|
means=means/subsetsize;
|
|
for(i=0; i<=subsetsize-1; i++)
|
|
{
|
|
CSparse::SparseGetRow(xy,idx[i],network.m_xyrow);
|
|
sigmas+=MathPow(network.m_xyrow-means+0,2.0);
|
|
}
|
|
sigmas=MathSqrt(sigmas/subsetsize);
|
|
//--- Inputs
|
|
for(i=0; i<nin; i++)
|
|
{
|
|
network.m_columnmeans.Set(i,means[i]);
|
|
network.m_columnsigmas.Set(i,(sigmas[i]!=0?sigmas[i]:1));
|
|
}
|
|
//--- Outputs
|
|
if(!MLPIsSoftMax(network))
|
|
{
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
offs=istart+(ntotal-nout+i)*m_nfieldwidth;
|
|
ntype=network.m_structinfo[offs];
|
|
//--- Linear outputs
|
|
if(ntype==0)
|
|
{
|
|
network.m_columnmeans.Set(nin+i,means[nin+i]);
|
|
network.m_columnsigmas.Set(nin+i,(sigmas[nin+i]!=0.0?sigmas[nin+i]:1.0));
|
|
}
|
|
//--- Bounded outputs (half-interval)
|
|
if(ntype==3)
|
|
{
|
|
s=means[nin+i]-network.m_columnmeans[nin+i];
|
|
if(s==0.0)
|
|
s=MathSign(network.m_columnsigmas[nin+i]);
|
|
if(s==0.0)
|
|
s=1.0;
|
|
s=MathSign(network.m_columnsigmas[nin+i])*MathAbs(s);
|
|
network.m_columnsigmas.Set(nin+i,(s!=0?s:1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns information about initialized network: number of inputs, |
|
|
//| outputs, weights. |
|
|
//+------------------------------------------------------------------+
|
|
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];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns number of "internal", low-level neurons in the network |
|
|
//| (one which is stored in StructInfo). |
|
|
//+------------------------------------------------------------------+
|
|
int CMLPBase::MLPNTotal(CMultilayerPerceptron &network)
|
|
{
|
|
return(network.m_structinfo[3]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns number of inputs. |
|
|
//+------------------------------------------------------------------+
|
|
int CMLPBase::MLPGetInputsCount(CMultilayerPerceptron &network)
|
|
{
|
|
return(network.m_structinfo[1]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns number of outputs. |
|
|
//+------------------------------------------------------------------+
|
|
int CMLPBase::MLPGetOutputsCount(CMultilayerPerceptron &network)
|
|
{
|
|
return(network.m_structinfo[2]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns number of weights. |
|
|
//+------------------------------------------------------------------+
|
|
int CMLPBase::MLPGetWeightsCount(CMultilayerPerceptron &network)
|
|
{
|
|
return(network.m_structinfo[4]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Tells whether network is SOFTMAX-normalized (i.m_e. classifier) |
|
|
//| or not. |
|
|
//+------------------------------------------------------------------+
|
|
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). |
|
|
//+------------------------------------------------------------------+
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
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 |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGetInputScaling(CMultilayerPerceptron &network,
|
|
const int i,double &mean,
|
|
double &sigma)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(i>=0 && i<network.m_hllayersizes[0],__FUNCTION__+": incorrect (nonexistent) I"))
|
|
{
|
|
mean=0;
|
|
sigma=0;
|
|
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). |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGetOutputScaling(CMultilayerPerceptron &network,
|
|
const int i,double &mean,
|
|
double &sigma)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(i>=0 && i<network.m_hllayersizes[CAp::Len(network.m_hllayersizes)-1],__FUNCTION__+": incorrect (nonexistent) I"))
|
|
{
|
|
mean=0;
|
|
sigma=0;
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
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=network.m_hlneurons.Size()/m_hlm_nfieldwidth;
|
|
istart=network.m_structinfo[5];
|
|
//--- search
|
|
network.m_integerbuf.Set(0,k);
|
|
network.m_integerbuf.Set(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 |
|
|
//+------------------------------------------------------------------+
|
|
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.Set(0,k0);
|
|
network.m_integerbuf.Set(1,i0);
|
|
network.m_integerbuf.Set(2,k1);
|
|
network.m_integerbuf.Set(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.|
|
|
//+------------------------------------------------------------------+
|
|
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(MathIsValidNumber(mean),__FUNCTION__+": infinite or NAN Mean"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(sigma),__FUNCTION__+": infinite or NAN Sigma"))
|
|
return;
|
|
//--- check
|
|
if(sigma==0.0)
|
|
sigma=1;
|
|
//--- change values
|
|
network.m_columnmeans.Set(i,mean);
|
|
network.m_columnsigmas.Set(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. |
|
|
//+------------------------------------------------------------------+
|
|
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(MathIsValidNumber(mean),__FUNCTION__+": infinite or NAN Mean"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(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.Set(network.m_hllayersizes[0]+i,mean);
|
|
network.m_columnsigmas.Set(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). |
|
|
//+------------------------------------------------------------------+
|
|
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(MathIsValidNumber(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.Set(0,k);
|
|
network.m_integerbuf.Set(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.Set(activationoffset,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.Set(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 |
|
|
//+------------------------------------------------------------------+
|
|
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(MathIsValidNumber(w),__FUNCTION__+": infinite or NAN weight"))
|
|
return;
|
|
//--- search
|
|
network.m_integerbuf.Set(0,k0);
|
|
network.m_integerbuf.Set(1,i0);
|
|
network.m_integerbuf.Set(2,k1);
|
|
network.m_integerbuf.Set(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.Set(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 |
|
|
//+------------------------------------------------------------------+
|
|
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;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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 |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPProcess(CMultilayerPerceptron &network,double &x[],
|
|
double &y[])
|
|
{
|
|
//--- check
|
|
CRowDouble X=x;
|
|
CRowDouble Y=vector<double>::Zeros(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);
|
|
//--- return result
|
|
Y.ToArray(y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPProcess(CMultilayerPerceptron &network,CRowDouble &x,
|
|
CRowDouble &y)
|
|
{
|
|
//--- check
|
|
if(y.Size()<network.m_structinfo[2])
|
|
y.Resize(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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPProcessI(CMultilayerPerceptron &network,double &x[],
|
|
double &y[])
|
|
{
|
|
MLPProcess(network,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPProcessI(CMultilayerPerceptron &network,CRowDouble &x,
|
|
CRowDouble &y)
|
|
{
|
|
MLPProcess(network,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Error function for neural network,internal subroutine. |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPError(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int npoints)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": XY has less than NPoints rows"))
|
|
return(result);
|
|
if(npoints>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
|
|
return(result);
|
|
}
|
|
else
|
|
if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
|
|
return(result);
|
|
}
|
|
//--- function call
|
|
MLPAllErrorsX(network,xy,network.m_dummysxy,npoints,0,network.m_dummyidx,0,npoints,0,network.m_err);
|
|
result=CMath::Sqr(network.m_err.m_RMSError)*npoints*MLPGetOutputsCount(network)/2;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Error of the neural network on dataset given by sparse matrix. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - neural network |
|
|
//| XY - training set, see below for information on the |
|
|
//| training set format. This function checks |
|
|
//| correctness of the dataset (no NANs/INFs, class |
|
|
//| numbers are correct) and throws exception when |
|
|
//| incorrect dataset is passed. Sparse matrix must |
|
|
//| use CRS format for storage. |
|
|
//| NPoints - points count, >=0 |
|
|
//| RESULT: |
|
|
//| sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) |
|
|
//| DATASET FORMAT: |
|
|
//| This function uses two different dataset formats - one for |
|
|
//| regression networks, another one for classification networks. |
|
|
//| For regression networks with NIn inputs and NOut outputs |
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+NOut) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, next NOut columns are outputs |
|
|
//| For classification networks with NIn inputs and NClasses clases |
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+1) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, last column stores class number|
|
|
//| (from 0 to NClasses-1). |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPErrorSparse(CMultilayerPerceptron &network,
|
|
CSparseMatrix &xy,int npoints)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": XY is not in CRS format."))
|
|
return(result);
|
|
if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=npoints,__FUNCTION__": XY has less than NPoints rows"))
|
|
return(result);
|
|
|
|
if(npoints>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
|
|
return(result);
|
|
}
|
|
else
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
|
|
return(result);
|
|
}
|
|
MLPAllErrorsX(network,network.m_dummydxy,xy,npoints,1,network.m_dummyidx,0,npoints,0,network.m_err);
|
|
result=CMath::Sqr(network.m_err.m_RMSError)*npoints*MLPGetOutputsCount(network)/2;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Natural error function for neural network,internal subroutine. |
|
|
//+------------------------------------------------------------------+
|
|
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);
|
|
matrix<double> splitted[];
|
|
ulong parts[]={nin,nout};
|
|
xy.Split(parts,1,splitted);
|
|
//--- calculation
|
|
for(i=0; i<ssize; i++)
|
|
{
|
|
//--- Process vector
|
|
network.m_x=splitted[0].Row(i);
|
|
//--- function call
|
|
MLPProcess(network,network.m_x,network.m_y);
|
|
//--- Update error function
|
|
if(network.m_structinfo[6]==0)
|
|
{
|
|
//--- Least squares error function
|
|
network.m_y-=splitted[1].Row(i);
|
|
//--- calculation
|
|
e=network.m_y.Dot(network.m_y);
|
|
result+=e/2;
|
|
}
|
|
else
|
|
{
|
|
//--- Cross-entropy error function
|
|
k=(int)MathRound(splitted[1][i,0]);
|
|
//--- check
|
|
if(k>=0 && k<nout)
|
|
result+=SafeCrossEntropy(1,network.m_y[k]);
|
|
}
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Classification error |
|
|
//+------------------------------------------------------------------+
|
|
int CMLPBase::MLPClsError(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int npoints)
|
|
{
|
|
int result=0;
|
|
//--- check
|
|
if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": XY has less than NPoints rows"))
|
|
return(result);
|
|
if(npoints>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
|
|
return(result);
|
|
}
|
|
else
|
|
if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
|
|
return(result);
|
|
}
|
|
//--- function call
|
|
MLPAllErrorsX(network,xy,network.m_dummysxy,npoints,0,network.m_dummyidx,0,npoints,0,network.m_err);
|
|
result=(int)MathRound(npoints*network.m_err.m_RelCLSError);
|
|
//--- 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. |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPRelClsError(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int npoints)
|
|
{
|
|
return((double)MLPClsError(network,xy,npoints)/(double)npoints);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Relative classification error on the test set given by sparse |
|
|
//| matrix. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - neural network; |
|
|
//| XY - training set, see below for information on the |
|
|
//| training set format. Sparse matrix must use CRS |
|
|
//| format for storage. |
|
|
//| NPoints - points count, >=0. |
|
|
//| RESULT: |
|
|
//| Percent of incorrectly classified cases. Works both for |
|
|
//| classifier networks and general purpose networks used as |
|
|
//| classifiers. |
|
|
//| DATASET FORMAT: |
|
|
//| This function uses two different dataset formats - one for |
|
|
//| regression networks, another one for classification networks. |
|
|
//| For regression networks with NIn inputs and NOut outputs |
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+NOut) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, next NOut columns are |
|
|
//| outputs |
|
|
//| For classification networks with NIn inputs and NClasses clases|
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+1) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, last column stores class |
|
|
//| number (from 0 to NClasses-1). |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPRelClsErrorSparse(CMultilayerPerceptron &network,
|
|
CSparseMatrix &xy,int npoints)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY is not in CRS format."))
|
|
return(result);
|
|
if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=npoints,__FUNCTION__": sparse matrix XY has less than NPoints rows"))
|
|
return(result);
|
|
if(npoints>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+1 columns"))
|
|
return(result);
|
|
}
|
|
else
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+NOut columns"))
|
|
return(result);
|
|
}
|
|
//--- function call
|
|
MLPAllErrorsX(network,network.m_dummydxy,xy,npoints,1,network.m_dummyidx,0,npoints,0,network.m_err);
|
|
//--- return result
|
|
return(network.m_err.m_RelCLSError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPAvgCE(CMultilayerPerceptron &network,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": XY has less than NPoints rows"))
|
|
return(result);
|
|
if(npoints>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
|
|
return(result);
|
|
}
|
|
else
|
|
if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
|
|
return(result);
|
|
}
|
|
//--- function call
|
|
MLPAllErrorsX(network,xy,network.m_dummysxy,npoints,0,network.m_dummyidx,0,npoints,0,network.m_err);
|
|
//--- return result
|
|
return(network.m_err.m_AvgCE);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Average cross-entropy (in bits per element) on the test set given|
|
|
//| by sparse matrix. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - neural network; |
|
|
//| XY - training set, see below for information on the |
|
|
//| training set format. This function checks |
|
|
//| correctness of the dataset (no NANs/INFs, class |
|
|
//| numbers are correct) and throws exception when |
|
|
//| incorrect dataset is passed. Sparse matrix must |
|
|
//| use CRS format for storage. |
|
|
//| NPoints - points count, >=0. |
|
|
//| RESULT: |
|
|
//| CrossEntropy/(NPoints*LN(2)). |
|
|
//| Zero if network solves regression task. |
|
|
//| DATASET FORMAT: |
|
|
//| This function uses two different dataset formats - one for |
|
|
//| regression networks, another one for classification networks. |
|
|
//| For regression networks with NIn inputs and NOut outputs |
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+NOut) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, next NOut columns are |
|
|
//| outputs |
|
|
//| For classification networks with NIn inputs and NClasses clases|
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+1) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, last column stores class |
|
|
//| number (from 0 to NClasses-1). |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPAvgCESparse(CMultilayerPerceptron &network,
|
|
CSparseMatrix &xy,int npoints)
|
|
{
|
|
double result=0;
|
|
//--- check
|
|
if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY is not in CRS format."))
|
|
return(result);
|
|
if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=npoints,__FUNCTION__": sparse matrix XY has less than NPoints rows"))
|
|
return(result);
|
|
if(npoints>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+1 columns"))
|
|
return(result);
|
|
}
|
|
else
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+NOut columns"))
|
|
return(result);
|
|
}
|
|
//--- function call
|
|
MLPAllErrorsX(network,network.m_dummydxy,xy,npoints,1,network.m_dummyidx,0,npoints,0,network.m_err);
|
|
//--- return result
|
|
return(network.m_err.m_AvgCE);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPRMSError(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int npoints)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": XY has less than NPoints rows"))
|
|
return(0);
|
|
if(npoints>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
|
|
return(0);
|
|
}
|
|
else
|
|
if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
|
|
return(0);
|
|
}
|
|
//--- function call
|
|
MLPAllErrorsX(network,xy,network.m_dummysxy,npoints,0,network.m_dummyidx,0,npoints,0,network.m_err);
|
|
//--- return result
|
|
return(network.m_err.m_RMSError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| RMS error on the test set given by sparse matrix. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - neural network; |
|
|
//| XY - training set, see below for information on the |
|
|
//| training set format. This function checks |
|
|
//| correctness of the dataset (no NANs/INFs, class |
|
|
//| numbers are correct) and throws exception when |
|
|
//| incorrect dataset is passed. Sparse matrix must |
|
|
//| use CRS format for storage. |
|
|
//| NPoints - points count, >=0. |
|
|
//| RESULT: |
|
|
//| Root mean square error. Its meaning for regression task is |
|
|
//| obvious. As for classification task, RMS error means error when|
|
|
//| estimating posterior probabilities. |
|
|
//| DATASET FORMAT: |
|
|
//| This function uses two different dataset formats - one for |
|
|
//| regression networks, another one for classification networks. |
|
|
//| For regression networks with NIn inputs and NOut outputs |
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn + NOut) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, next NOut columns are |
|
|
//| outputs |
|
|
//| For classification networks with NIn inputs and NClasses |
|
|
//| clases following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn + 1) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, last column stores class |
|
|
//| number(from 0 to NClasses - 1). |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPRMSErrorSparse(CMultilayerPerceptron &network,
|
|
CSparseMatrix &xy,int npoints)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY is not in CRS format."))
|
|
return(0);
|
|
if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=npoints,__FUNCTION__": sparse matrix XY has less than NPoints rows"))
|
|
return(0);
|
|
|
|
if(npoints>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+1 columns"))
|
|
return(0);
|
|
}
|
|
else
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+NOut columns"))
|
|
return(0);
|
|
}
|
|
//---function call
|
|
MLPAllErrorsX(network,network.m_dummydxy,xy,npoints,1,network.m_dummyidx,0,npoints,0,network.m_err);
|
|
//--- return result
|
|
return(network.m_err.m_RMSError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPAvgError(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int npoints)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": XY has less than NPoints rows"))
|
|
return(0);
|
|
|
|
if(npoints>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
|
|
return(0);
|
|
}
|
|
else
|
|
if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
|
|
return(0);
|
|
}
|
|
//--- function call
|
|
MLPAllErrorsX(network,xy,network.m_dummysxy,npoints,0,network.m_dummyidx,0,npoints,0,network.m_err);
|
|
//--- return result
|
|
return(network.m_err.m_AvgError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Average absolute error on the test set given by sparse matrix. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - neural network; |
|
|
//| XY - training set, see below for information on the |
|
|
//| training set format. This function checks |
|
|
//| correctness of the dataset (no NANs/INFs, class |
|
|
//| numbers are correct) and throws exception when |
|
|
//| incorrect dataset is passed. Sparse matrix must |
|
|
//| use CRS format for storage. |
|
|
//| NPoints - points count, >=0. |
|
|
//| RESULT: |
|
|
//| Its meaning for regression task is obvious. As for |
|
|
//| classification task, it means average error when estimating |
|
|
//| posterior probabilities. |
|
|
//| DATASET FORMAT: |
|
|
//| This function uses two different dataset formats - one for |
|
|
//| regression networks, another one for classification networks. |
|
|
//| For regression networks with NIn inputs and NOut outputs |
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+NOut) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, next NOut columns are |
|
|
//| outputs |
|
|
//| For classification networks with NIn inputs and NClasses clases|
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+1) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, last column stores class |
|
|
//| number (from 0 to NClasses-1). |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPAvgErrorSparse(CMultilayerPerceptron &network,
|
|
CSparseMatrix &xy,int npoints)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY is not in CRS format."))
|
|
return(0);
|
|
if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=npoints,__FUNCTION__": sparse matrix XY has less than NPoints rows"))
|
|
return(0);
|
|
|
|
if(npoints>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+1 columns"))
|
|
return(0);
|
|
}
|
|
else
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+NOut columns"))
|
|
return(0);
|
|
}
|
|
//---function call
|
|
MLPAllErrorsX(network,network.m_dummydxy,xy,npoints,1,network.m_dummyidx,0,npoints,0,network.m_err);
|
|
//--- return result
|
|
return(network.m_err.m_AvgError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPAvgRelError(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int npoints)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": XY has less than NPoints rows"))
|
|
return(0);
|
|
|
|
if(npoints>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
|
|
return(0);
|
|
}
|
|
else
|
|
if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
|
|
return(0);
|
|
}
|
|
//--- function call
|
|
MLPAllErrorsX(network,xy,network.m_dummysxy,npoints,0,network.m_dummyidx,0,npoints,0,network.m_err);
|
|
//--- return result
|
|
return(network.m_err.m_AvgRelError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Average relative error on the test set given by sparse matrix. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - neural network; |
|
|
//| XY - training set, see below for information on the |
|
|
//| training set format. This function checks |
|
|
//| correctness of the dataset (no NANs/INFs, class |
|
|
//| numbers are correct) and throws exception when |
|
|
//| incorrect dataset is passed. Sparse matrix must |
|
|
//| use CRS format for storage. |
|
|
//| NPoints - points count, >=0. |
|
|
//| 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. |
|
|
//| DATASET FORMAT: |
|
|
//| This function uses two different dataset formats - one for |
|
|
//| regression networks, another one for classification networks. |
|
|
//| For regression networks with NIn inputs and NOut outputs |
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+NOut) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, next NOut columns are |
|
|
//| outputs |
|
|
//| For classification networks with NIn inputs and NClasses clases|
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+1) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, last column stores class |
|
|
//| number (from 0 to NClasses-1). |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPAvgRelErrorSparse(CMultilayerPerceptron &network,
|
|
CSparseMatrix &xy,int npoints)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY is not in CRS format."))
|
|
return(0);
|
|
if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=npoints,__FUNCTION__": sparse matrix XY has less than NPoints rows"))
|
|
return(0);
|
|
|
|
if(npoints>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+1 columns"))
|
|
return(0);
|
|
}
|
|
else
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": sparse matrix XY has less than NIn+NOut columns"))
|
|
return(0);
|
|
}
|
|
//---function call
|
|
MLPAllErrorsX(network,network.m_dummydxy,xy,npoints,1,network.m_dummyidx,0,npoints,0,network.m_err);
|
|
//--- return result
|
|
return(network.m_err.m_AvgRelError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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] |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGrad(CMultilayerPerceptron &network,double &x[],
|
|
double &desiredy[],double &e,double &grad[])
|
|
{
|
|
CRowDouble X=x;
|
|
CRowDouble Y=desiredy;
|
|
CRowDouble Grad;
|
|
MLPGrad(network,X,Y,e,Grad);
|
|
Grad.ToArray(grad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGrad(CMultilayerPerceptron &network,CRowDouble &x,
|
|
CRowDouble &desiredy,double &e,CRowDouble &grad)
|
|
{
|
|
//--- create variables
|
|
int nout=0;
|
|
int ntotal=0;
|
|
//--- Alloc
|
|
if(CAp::Len(grad)<network.m_structinfo[4])
|
|
grad.Resize(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=MathPow(network.m_y.ToVector()-desiredy.ToVector(),2.0).Sum()/2.0;
|
|
//--- change values
|
|
network.m_derror=vector<double>::Zeros(ntotal);
|
|
for(int i=0; i<nout; i++)
|
|
network.m_derror.Set(ntotal-nout+i,network.m_y[i]-desiredy[i]);
|
|
//--- 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] |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGradN(CMultilayerPerceptron &network,double &x[],
|
|
double &desiredy[],double &e,double &grad[])
|
|
{
|
|
CRowDouble X=x;
|
|
CRowDouble Y=desiredy;
|
|
CRowDouble Grad;
|
|
MLPGradN(network,X,Y,e,Grad);
|
|
Grad.ToArray(grad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGradN(CMultilayerPerceptron &network,CRowDouble &x,
|
|
CRowDouble &desiredy,double &e,CRowDouble &grad)
|
|
{
|
|
//--- create variables
|
|
double s=0;
|
|
int nout=0;
|
|
int ntotal=0;
|
|
//--- initialization
|
|
e=0;
|
|
//--- Alloc
|
|
if(grad.Size()<network.m_structinfo[4])
|
|
grad.Resize(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];
|
|
network.m_derror=vector<double>::Zeros(ntotal);
|
|
e=0;
|
|
//--- check
|
|
if(network.m_structinfo[6]==0)
|
|
{
|
|
//--- Regression network,least squares
|
|
e=MathPow(network.m_y.ToVector()-desiredy.ToVector(),2.0).Sum()/2.0;
|
|
for(int i=0; i<=nout-1; i++)
|
|
network.m_derror.Set(ntotal-nout+i,network.m_y[i]-desiredy[i]);
|
|
}
|
|
else
|
|
{
|
|
//--- Classification network,cross-entropy
|
|
s=desiredy.Sum();
|
|
for(int i=0; i<nout; i++)
|
|
{
|
|
network.m_derror.Set(ntotal-nout+i,s*network.m_y[i]-desiredy[i]);
|
|
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] |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGradBatch(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int ssize,
|
|
double &e,double &grad[])
|
|
{
|
|
CRowDouble Grad;
|
|
MLPGradBatch(network,xy,ssize,e,Grad);
|
|
Grad.ToArray(grad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGradBatch(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int ssize,
|
|
double &e,CRowDouble &grad)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
//--- function call
|
|
MLPProperties(network,nin,nout,wcount);
|
|
//--- initialization
|
|
grad=vector<double>::Zeros(wcount);
|
|
e=0;
|
|
//--- function call
|
|
MLPGradBatchX(network,xy,network.m_dummysxy,ssize,0,network.m_dummyidx,0,ssize,0,e,grad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Batch gradient calculation for a set of inputs/outputs given by |
|
|
//| sparse matrices |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - network initialized with one of the network |
|
|
//| creation funcs |
|
|
//| XY - original dataset in sparse format; one sample = one|
|
|
//| row: |
|
|
//| * MATRIX MUST BE STORED IN CRS FORMAT |
|
|
//| * first NIn columns contain inputs. |
|
|
//| * for regression problem, next NOut columns store |
|
|
//| desired outputs. |
|
|
//| * for classification problem, next column (just |
|
|
//| one!) stores class number. |
|
|
//| 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] |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGradBatchSparse(CMultilayerPerceptron &network,
|
|
CSparseMatrix &xy,int ssize,
|
|
double &e,CRowDouble &grad)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int subset0=0;
|
|
int subset1=ssize;
|
|
int subsettype=0;
|
|
e=0;
|
|
//--- check
|
|
if(!CAp::Assert(ssize>=0,__FUNCTION__": SSize<0"))
|
|
return;
|
|
if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY must be in CRS format."))
|
|
return;
|
|
|
|
MLPProperties(network,nin,nout,wcount);
|
|
grad=vector<double>::Zeros(wcount);
|
|
MLPGradBatchX(network,network.m_dummydxy,xy,ssize,1,network.m_dummyidx,subset0,subset1,subsettype,e,grad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Batch gradient calculation for a subset of dataset |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - network initialized with one of the network |
|
|
//| creation funcs |
|
|
//| XY - original dataset in dense format; one sample = one |
|
|
//| row: |
|
|
//| * first NIn columns contain inputs, |
|
|
//| * for regression problem, next NOut columns store |
|
|
//| desired outputs. |
|
|
//| * for classification problem, next column (just |
|
|
//| one!) stores class number. |
|
|
//| SetSize - real size of XY, SetSize>=0; |
|
|
//| Idx - subset of SubsetSize elements, array[SubsetSize]: |
|
|
//| * Idx[I] stores row index in the original dataset |
|
|
//| which is given by XY. Gradient is calculated with|
|
|
//| respect to rows whose indexes are stored in Idx[]|
|
|
//| * Idx[] must store correct indexes; this function |
|
|
//| throws an exception in case incorrect index (less|
|
|
//| than 0 or larger than rows(XY)) is given |
|
|
//| * Idx[] may store indexes in any order and even |
|
|
//| with repetitions. |
|
|
//| SubsetSize- number of elements in Idx[] array: |
|
|
//| * positive value means that subset given by Idx[] |
|
|
//| is processed |
|
|
//| * zero value results in zero gradient |
|
|
//| * negative value means that full dataset is |
|
|
//| processed |
|
|
//| 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] |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGradBatchSubset(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,int setsize,
|
|
CRowInt &idx,int subsetsize,
|
|
double &e,CRowDouble &grad)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int npoints=0;
|
|
int subset0=0;
|
|
int subset1=0;
|
|
int subsettype=0;
|
|
e=0;
|
|
//--- check
|
|
if(!CAp::Assert(setsize>=0,__FUNCTION__": SetSize<0"))
|
|
return;
|
|
if(!CAp::Assert(subsetsize<=idx.Size(),__FUNCTION__": SubsetSize>Length(Idx)"))
|
|
return;
|
|
|
|
npoints=setsize;
|
|
if(subsetsize<0)
|
|
{
|
|
subset0=0;
|
|
subset1=setsize;
|
|
subsettype=0;
|
|
}
|
|
else
|
|
{
|
|
subset0=0;
|
|
subset1=subsetsize;
|
|
subsettype=1;
|
|
for(int i=0; i<subsetsize; i++)
|
|
{
|
|
if(!CAp::Assert(idx[i]>=0,__FUNCTION__": incorrect index of XY row(Idx[I]<0)"))
|
|
return;
|
|
if(!CAp::Assert(idx[i]<npoints,__FUNCTION__": incorrect index of XY row(Idx[I]>Rows(XY)-1)"))
|
|
return;
|
|
}
|
|
}
|
|
MLPProperties(network,nin,nout,wcount);
|
|
grad=vector<double>::Zeros(wcount);
|
|
MLPGradBatchX(network,xy,network.m_dummysxy,setsize,0,idx,subset0,subset1,subsettype,e,grad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Batch gradient calculation for a set of inputs/outputs for a |
|
|
//| subset of dataset given by set of indexes. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - network initialized with one of the network |
|
|
//| creation funcs |
|
|
//| XY - original dataset in sparse format; one sample = one|
|
|
//| row: |
|
|
//| * MATRIX MUST BE STORED IN CRS FORMAT |
|
|
//| * first NIn columns contain inputs, |
|
|
//| * for regression problem, next NOut columns store |
|
|
//| desired outputs. |
|
|
//| * for classification problem, next column (just |
|
|
//| one!) stores class number. |
|
|
//| SetSize - real size of XY, SetSize>=0; |
|
|
//| Idx - subset of SubsetSize elements, array[SubsetSize]: |
|
|
//| * Idx[I] stores row index in the original dataset |
|
|
//| which is given by XY. Gradient is calculated with|
|
|
//| respect to rows whose indexes are stored in Idx[]|
|
|
//| * Idx[] must store correct indexes; this function |
|
|
//| throws an exception in case incorrect index (less|
|
|
//| than 0 or larger than rows(XY)) is given |
|
|
//| * Idx[] may store indexes in any order and even |
|
|
//| with repetitions. |
|
|
//| SubsetSize- number of elements in Idx[] array: |
|
|
//| * positive value means that subset given by Idx[] |
|
|
//| is processed |
|
|
//| * zero value results in zero gradient |
|
|
//| * negative value means that full dataset is |
|
|
//| processed |
|
|
//| 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] |
|
|
//| NOTE: when SubsetSize<0 is used full dataset by call |
|
|
//| MLPGradBatchSparse function. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGradBatchSparseSubset(CMultilayerPerceptron &network,
|
|
CSparseMatrix &xy,int setsize,
|
|
CRowInt &idx,int subsetsize,
|
|
double &e,CRowDouble &grad)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int npoints=0;
|
|
int subset0=0;
|
|
int subset1=0;
|
|
int subsettype=0;
|
|
e=0;
|
|
//--- check
|
|
if(!CAp::Assert(setsize>=0,__FUNCTION__": SetSize<0"))
|
|
return;
|
|
if(!CAp::Assert(subsetsize<=idx.Size(),__FUNCTION__": SubsetSize>Length(Idx)"))
|
|
return;
|
|
if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": sparse matrix XY must be in CRS format."))
|
|
return;
|
|
|
|
npoints=setsize;
|
|
if(subsetsize<0)
|
|
{
|
|
subset0=0;
|
|
subset1=setsize;
|
|
subsettype=0;
|
|
}
|
|
else
|
|
{
|
|
subset0=0;
|
|
subset1=subsetsize;
|
|
subsettype=1;
|
|
for(int i=0; i<subsetsize; i++)
|
|
{
|
|
if(!CAp::Assert(idx[i]>=0,__FUNCTION__": incorrect index of XY row(Idx[I]<0)"))
|
|
return;
|
|
if(!CAp::Assert(idx[i]<npoints,__FUNCTION__": incorrect index of XY row(Idx[I]>Rows(XY)-1)"))
|
|
return;
|
|
}
|
|
}
|
|
MLPProperties(network,nin,nout,wcount);
|
|
grad=vector<double>::Zeros(wcount);
|
|
MLPGradBatchX(network,network.m_dummydxy,xy,setsize,1,idx,subset0,subset1,subsettype,e,grad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal function which actually calculates batch gradient for a |
|
|
//| subset or full dataset, which can be represented in different |
|
|
//| formats. |
|
|
//| THIS FUNCTION IS NOT INTENDED TO BE USED BY ALGLIB USERS! |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGradBatchX(CMultilayerPerceptron &network,
|
|
CMatrixDouble &densexy,
|
|
CSparseMatrix &sparsexy,
|
|
int datasetsize,
|
|
int datasettype,
|
|
CRowInt &idx,
|
|
int subset0,
|
|
int subset1,
|
|
int subsettype,
|
|
double &e,
|
|
CRowDouble &grad)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int rowsize=0;
|
|
int srcidx=0;
|
|
int cstart=0;
|
|
int csize=0;
|
|
double problemcost=0;
|
|
CMLPBuffers buf2;
|
|
int len0=0;
|
|
int len1=0;
|
|
CMLPBuffers pbuf;
|
|
CSMLPGrad sgrad;
|
|
//--- check
|
|
if(!CAp::Assert(datasetsize>=0,__FUNCTION__": SetSize<0"))
|
|
return;
|
|
if(!CAp::Assert(datasettype==0 || datasettype==1,__FUNCTION__": DatasetType is incorrect"))
|
|
return;
|
|
if(!CAp::Assert(subsettype==0 || subsettype==1,__FUNCTION__": SubsetType is incorrect"))
|
|
return;
|
|
//--- Determine network and dataset properties
|
|
MLPProperties(network,nin,nout,wcount);
|
|
if(MLPIsSoftMax(network))
|
|
rowsize=nin+1;
|
|
else
|
|
rowsize=nin+nout;
|
|
//--- Chunked processing
|
|
CHPCCores::HPCPrepareChunkedGradient(network.m_weights,wcount,MLPNTotal(network),nin,nout,pbuf);
|
|
cstart=subset0;
|
|
while(cstart<subset1)
|
|
{
|
|
//--- Determine size of current chunk and copy it to PBuf.XY
|
|
csize=MathMin(subset1,cstart+pbuf.m_ChunkSize)-cstart;
|
|
for(int j=0; j<csize; j++)
|
|
{
|
|
srcidx=-1;
|
|
if(subsettype==0)
|
|
srcidx=cstart+j;
|
|
if(subsettype==1)
|
|
srcidx=idx[cstart+j];
|
|
if(!CAp::Assert(srcidx>=0,__FUNCTION__": internal error"))
|
|
return;
|
|
if(datasettype==0)
|
|
pbuf.m_XY.Row(j,densexy,srcidx);
|
|
if(datasettype==1)
|
|
{
|
|
CSparse::SparseGetRow(sparsexy,srcidx,pbuf.m_XYRow);
|
|
pbuf.m_XY.Row(j,pbuf.m_XYRow);
|
|
}
|
|
}
|
|
pbuf.m_XY.Resize(csize,rowsize);
|
|
//--- Process chunk and advance line pointer
|
|
MLPChunkedGradient(network,pbuf.m_XY,0,csize,pbuf.m_Batch4Buf,pbuf.m_HPCBuf,e,false);
|
|
cstart=cstart+pbuf.m_ChunkSize;
|
|
}
|
|
CHPCCores::HPCFinalizeChunkedGradient(pbuf,grad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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] |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGradNBatch(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int ssize,
|
|
double &e,double &grad[])
|
|
{
|
|
CRowDouble Grad;
|
|
MLPGradNBatch(network,xy,ssize,e,Grad);
|
|
Grad.ToArray(grad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPGradNBatch(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int ssize,
|
|
double &e,CRowDouble &grad)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
CMLPBuffers pbuf;
|
|
//--- function call
|
|
MLPProperties(network,nin,nout,wcount);
|
|
CHPCCores::HPCPrepareChunkedGradient(network.m_weights,wcount,MLPNTotal(network),nin,nout,pbuf);
|
|
//--- initialization
|
|
i=0;
|
|
e=0;
|
|
grad=vector<double>::Zeros(wcount);
|
|
//--- calculation
|
|
while(i<ssize)
|
|
{
|
|
MLPChunkedGradient(network,xy,i,MathMin(ssize,i+pbuf.m_ChunkSize)-i,pbuf.m_Batch4Buf,pbuf.m_HPCBuf,e,true);
|
|
i+=pbuf.m_ChunkSize;
|
|
}
|
|
CHPCCores::HPCFinalizeChunkedGradient(pbuf,grad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPHessianNBatch(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int ssize,
|
|
double &e,double &grad[],
|
|
CMatrixDouble &h)
|
|
{
|
|
//---
|
|
CRowDouble Grad;
|
|
//--- initialization
|
|
e=0;
|
|
//--- function call
|
|
MLPHessianBatchInternal(network,xy,ssize,true,e,Grad,h);
|
|
//--- return result
|
|
Grad.ToArray(grad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPHessianNBatch(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int ssize,
|
|
double &e,CRowDouble &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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPHessianBatch(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int ssize,
|
|
double &e,double &grad[],
|
|
CMatrixDouble &h)
|
|
{
|
|
CRowDouble Grad;
|
|
//--- initialization
|
|
e=0;
|
|
//--- function call
|
|
MLPHessianBatchInternal(network,xy,ssize,false,e,Grad,h);
|
|
//--- return result
|
|
Grad.ToArray(grad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPHessianBatch(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int ssize,
|
|
double &e,CRowDouble &grad,
|
|
CMatrixDouble &h)
|
|
{
|
|
//--- initialization
|
|
e=0;
|
|
//--- function call
|
|
MLPHessianBatchInternal(network,xy,ssize,false,e,grad,h);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine, shouldn't be called by user. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPInternalProcessVector(int &structinfo[],double &weights[],
|
|
double &columnmeans[],
|
|
double &columnsigmas[],
|
|
double &neurons[],
|
|
double &dfdnet[],
|
|
double &x[],double &y[])
|
|
{
|
|
//--- create variables
|
|
CRowInt StructInfo=structinfo;
|
|
CRowDouble Weights=weights;
|
|
CRowDouble ColumnMeans=columnmeans;
|
|
CRowDouble ColumnSigmas=columnsigmas;
|
|
CRowDouble X=x;
|
|
CRowDouble Neurons;
|
|
CRowDouble dFdnet;
|
|
CRowDouble Y;
|
|
//--- function call
|
|
MLPInternalProcessVector(StructInfo,Weights,ColumnMeans,ColumnSigmas,Neurons,dFdnet,X,Y);
|
|
//--- return result
|
|
Y.ToArray(y);
|
|
Neurons.ToArray(neurons);
|
|
dFdnet.ToArray(dfdnet);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPInternalProcessVector(CRowInt &structinfo,CRowDouble &weights,
|
|
CRowDouble &columnmeans,
|
|
CRowDouble &columnsigmas,
|
|
CRowDouble &neurons,
|
|
CRowDouble &dfdnet,
|
|
CRowDouble &x,CRowDouble &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; i++)
|
|
{
|
|
//--- check
|
|
if(columnsigmas[i]!=0.0)
|
|
neurons.Set(i,(x[i]-columnmeans[i])/columnsigmas[i]);
|
|
else
|
|
neurons.Set(i,x[i]-columnmeans[i]);
|
|
}
|
|
//--- Allocate
|
|
neurons.Resize(ntotal);
|
|
dfdnet.Resize(ntotal);
|
|
y.Resize(nout);
|
|
//--- Process network
|
|
for(i=0; i<ntotal; i++)
|
|
{
|
|
offs=istart+i*m_nfieldwidth;
|
|
//--- check
|
|
if(structinfo[offs]>0 || structinfo[offs]==-5)
|
|
{
|
|
//--- Activation function
|
|
MLPActivationFunction(neurons[structinfo[offs+2]],structinfo[offs],f,df,d2f);
|
|
//--- change values
|
|
neurons.Set(i,f);
|
|
dfdnet.Set(i,df);
|
|
continue;
|
|
}
|
|
//--- check
|
|
if(structinfo[offs]==0)
|
|
{
|
|
//--- Adaptive summator
|
|
n1=structinfo[offs+2];
|
|
w1=structinfo[offs+3];
|
|
w2=w1+structinfo[offs+1];
|
|
i1_=(n1)-(w1);
|
|
net=0.0;
|
|
//--- calculation
|
|
for(i_=w1; i_<w2; i_++)
|
|
net+=weights[i_]*neurons[i_+i1_];
|
|
neurons.Set(i,net);
|
|
dfdnet.Set(i,1.0);
|
|
continue;
|
|
}
|
|
//--- check
|
|
if(structinfo[offs]<0)
|
|
{
|
|
perr=true;
|
|
//--- check
|
|
switch(structinfo[offs])
|
|
{
|
|
case -2:
|
|
//--- input neuron,left unchanged
|
|
perr=false;
|
|
break;
|
|
//--- check
|
|
case -3:
|
|
//--- "-1" neuron
|
|
neurons.Set(i,-1);
|
|
perr=false;
|
|
break;
|
|
//--- check
|
|
case -4:
|
|
//--- "0" neuron
|
|
neurons.Set(i,0);
|
|
perr=false;
|
|
break;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(!perr,__FUNCTION__+": internal error - unknown neuron type!"))
|
|
return;
|
|
continue;
|
|
}
|
|
}
|
|
//--- Extract result
|
|
i1_=ntotal-nout;
|
|
for(i_=0; i_<nout; i_++)
|
|
y.Set(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.Max();
|
|
//--- calculation
|
|
y=MathExp(y-mx+0);
|
|
net=y.Sum();
|
|
y/= net;
|
|
}
|
|
else
|
|
{
|
|
//--- Standardisation
|
|
for(i=0; i<nout; i++)
|
|
y.Set(i,y[i]*columnsigmas[nin+i]+columnmeans[nin+i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: allocation |
|
|
//+------------------------------------------------------------------+
|
|
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[network.m_hllayersizes.Size()-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<network.m_hllayersizes.Size(); i++)
|
|
{
|
|
for(j=0; j<network.m_hllayersizes[i]; 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]; k++)
|
|
s.Alloc_Entry();
|
|
}
|
|
}
|
|
for(j=0; j<nin; j++)
|
|
{
|
|
//--- function call
|
|
MLPGetInputScaling(network,j,v0,v1);
|
|
//--- preparation to serialize
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
}
|
|
for(j=0; j<nout; j++)
|
|
{
|
|
//--- function call
|
|
MLPGetOutputScaling(network,j,v0,v1);
|
|
//--- preparation to serialize
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: serialization |
|
|
//+------------------------------------------------------------------+
|
|
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[network.m_hllayersizes.Size()-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); i++)
|
|
{
|
|
for(j=0; j<network.m_hllayersizes[i]; 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]; k++)
|
|
s.Serialize_Double(MLPGetWeight(network,i-1,k,i,j));
|
|
}
|
|
}
|
|
for(j=0; j<nin; j++)
|
|
{
|
|
//--- function call
|
|
MLPGetInputScaling(network,j,v0,v1);
|
|
//--- serializetion
|
|
s.Serialize_Double(v0);
|
|
s.Serialize_Double(v1);
|
|
}
|
|
for(j=0; j<nout; j++)
|
|
{
|
|
//--- function call
|
|
MLPGetOutputScaling(network,j,v0,v1);
|
|
//--- serializetion
|
|
s.Serialize_Double(v0);
|
|
s.Serialize_Double(v1);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: unserialization |
|
|
//+------------------------------------------------------------------+
|
|
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
|
|
CRowInt 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);
|
|
//--- change values
|
|
nin=layersizes[0];
|
|
nout=layersizes[layersizes.Size()-1];
|
|
//--- check
|
|
switch(layersizes.Size())
|
|
{
|
|
case 2:
|
|
//--- check
|
|
if(issoftmax)
|
|
MLPCreateC0(layersizes[0],layersizes[1],network);
|
|
else
|
|
MLPCreate0(layersizes[0],layersizes[1],network);
|
|
break;
|
|
case 3:
|
|
//--- check
|
|
if(issoftmax)
|
|
MLPCreateC1(layersizes[0],layersizes[1],layersizes[2],network);
|
|
else
|
|
MLPCreate1(layersizes[0],layersizes[1],layersizes[2],network);
|
|
break;
|
|
case 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);
|
|
break;
|
|
default:
|
|
//--- check
|
|
if(!CAp::Assert(layersizes.Size()==2 || layersizes.Size()==3 || layersizes.Size()==4,__FUNCTION__+": too many hidden layers!"))
|
|
return;
|
|
}
|
|
//--- Load neurons and weights
|
|
for(i=1; i<layersizes.Size(); i++)
|
|
{
|
|
for(j=0; j<layersizes[i]; 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]; k++)
|
|
{
|
|
v0=s.Unserialize_Double();
|
|
//--- function call
|
|
MLPSetWeight(network,i-1,k,i,j,v0);
|
|
}
|
|
}
|
|
}
|
|
//
|
|
//--- Load standartizator
|
|
//
|
|
for(j=0; j<nin; j++)
|
|
{
|
|
//--- unserializetion
|
|
v0=s.Unserialize_Double();
|
|
v1=s.Unserialize_Double();
|
|
//--- function call
|
|
MLPSetInputScaling(network,j,v0,v1);
|
|
}
|
|
for(j=0; j<nout; j++)
|
|
{
|
|
//--- unserializetion
|
|
v0=s.Unserialize_Double();
|
|
v1=s.Unserialize_Double();
|
|
//--- function call
|
|
MLPSetOutputScaling(network,j,v0,v1);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Calculation of all types of errors on subset of dataset. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - network initialized with one of the network |
|
|
//| creation funcs |
|
|
//| XY - original dataset; one sample = one row; |
|
|
//| first NIn columns contain inputs, next NOut |
|
|
//| columns - desired outputs. |
|
|
//| SetSize - real size of XY, SetSize>=0; |
|
|
//| Subset - subset of SubsetSize elements, array[SubsetSize]; |
|
|
//| SubsetSize- number of elements in Subset[] array: |
|
|
//| * if SubsetSize>0, rows of XY with indices |
|
|
//| Subset[0]......Subset[SubsetSize-1] are processed|
|
|
//| * if SubsetSize=0, zeros are returned |
|
|
//| * if SubsetSize<0, entire dataset is processed; |
|
|
//| Subset[] array is ignored in this case. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Rep - it contains all type of errors. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPAllErrorsSubset(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,int setsize,
|
|
CRowInt &subset,int subsetsize,
|
|
CModelErrors &rep)
|
|
{
|
|
//--- create variables
|
|
int idx0=0;
|
|
int idx1=0;
|
|
int idxtype=0;
|
|
//--- check
|
|
if(!CAp::Assert(xy.Rows()>=setsize,__FUNCTION__": XY has less than SetSize rows"))
|
|
return;
|
|
if(setsize>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
|
|
return;
|
|
}
|
|
else
|
|
if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
|
|
return;
|
|
}
|
|
if(subsetsize>=0)
|
|
{
|
|
idx0=0;
|
|
idx1=subsetsize;
|
|
idxtype=1;
|
|
}
|
|
else
|
|
{
|
|
idx0=0;
|
|
idx1=setsize;
|
|
idxtype=0;
|
|
}
|
|
MLPAllErrorsX(network,xy,network.m_dummysxy,setsize,0,subset,idx0,idx1,idxtype,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Calculation of all types of errors on subset of dataset. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - network initialized with one of the network |
|
|
//| creation funcs |
|
|
//| XY - original dataset given by sparse matrix; |
|
|
//| one sample = one row; first NIn columns contain |
|
|
//| inputs, next NOut columns - desired outputs. |
|
|
//| SetSize - real size of XY, SetSize>=0; |
|
|
//| Subset - subset of SubsetSize elements, array[SubsetSize]; |
|
|
//| SubsetSize- number of elements in Subset[] array: |
|
|
//| * if SubsetSize>0, rows of XY with indices |
|
|
//| Subset[0]......Subset[SubsetSize-1] are processed|
|
|
//| * if SubsetSize=0, zeros are returned |
|
|
//| * if SubsetSize<0, entire dataset is processed; |
|
|
//| Subset[] array is ignored in this case. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Rep - it contains all type of errors. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPAllErrorsSparseSubset(CMultilayerPerceptron &network,
|
|
CSparseMatrix &xy,int setsize,
|
|
CRowInt &subset,int subsetsize,
|
|
CModelErrors &rep)
|
|
{
|
|
//--- create variables
|
|
int idx0=0;
|
|
int idx1=0;
|
|
int idxtype=0;
|
|
//--- check
|
|
if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": XY is not in CRS format."))
|
|
return;
|
|
if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=setsize,__FUNCTION__": XY has less than SetSize rows"))
|
|
return;
|
|
if(setsize>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
|
|
return;
|
|
}
|
|
else
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
|
|
return;
|
|
}
|
|
if(subsetsize>=0)
|
|
{
|
|
idx0=0;
|
|
idx1=subsetsize;
|
|
idxtype=1;
|
|
}
|
|
else
|
|
{
|
|
idx0=0;
|
|
idx1=setsize;
|
|
idxtype=0;
|
|
}
|
|
MLPAllErrorsX(network,network.m_dummydxy,xy,setsize,1,subset,idx0,idx1,idxtype,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Error of the neural network on subset of dataset. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - neural network; |
|
|
//| XY - training set, see below for information on the |
|
|
//| training set format; |
|
|
//| SetSize - real size of XY, SetSize>=0; |
|
|
//| Subset - subset of SubsetSize elements, array[SubsetSize]; |
|
|
//| SubsetSize- number of elements in Subset[] array: |
|
|
//| * if SubsetSize>0, rows of XY with indices |
|
|
//| Subset[0]......Subset[SubsetSize-1] are processed|
|
|
//| * if SubsetSize=0, zeros are returned |
|
|
//| * if SubsetSize<0, entire dataset is processed; |
|
|
//| Subset[] array is ignored in this case. |
|
|
//| RESULT: |
|
|
//| sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) |
|
|
//| DATASET FORMAT: |
|
|
//| This function uses two different dataset formats - one for |
|
|
//| regression networks, another one for classification networks. |
|
|
//| For regression networks with NIn inputs and NOut outputs |
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+NOut) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, next NOut columns are |
|
|
//| outputs |
|
|
//| For classification networks with NIn inputs and NClasses clases|
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+1) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, last column stores class |
|
|
//| number (from 0 to NClasses-1). |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPErrorSubset(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,int setsize,
|
|
CRowInt &subset,int subsetsize)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int idx0=0;
|
|
int idx1=0;
|
|
int idxtype=0;
|
|
//--- check
|
|
if(!CAp::Assert(xy.Rows()>=setsize,__FUNCTION__": XY has less than SetSize rows"))
|
|
return(0);
|
|
if(setsize>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(xy.Cols()>MLPGetInputsCount(network),__FUNCTION__": XY has less than NIn+1 columns"))
|
|
return(0);
|
|
}
|
|
else
|
|
if(!CAp::Assert(xy.Cols()>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
|
|
return(0);
|
|
}
|
|
if(subsetsize>=0)
|
|
{
|
|
idx0=0;
|
|
idx1=subsetsize;
|
|
idxtype=1;
|
|
}
|
|
else
|
|
{
|
|
idx0=0;
|
|
idx1=setsize;
|
|
idxtype=0;
|
|
}
|
|
//--- function call
|
|
MLPAllErrorsX(network,xy,network.m_dummysxy,setsize,0,subset,idx0,idx1,idxtype,network.m_err);
|
|
result=CMath::Sqr(network.m_err.m_RMSError)*(idx1-idx0)*MLPGetOutputsCount(network)/2;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Error of the neural network on subset of sparse dataset. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Network - neural network; |
|
|
//| XY - training set, see below for information on the |
|
|
//| training set format. This function checks |
|
|
//| correctness of the dataset (no NANs/INFs, class |
|
|
//| numbers are correct) and throws exception when |
|
|
//| incorrect dataset is passed. Sparse matrix must use|
|
|
//| CRS format for storage. |
|
|
//| SetSize - real size of XY, SetSize>=0; it is used when |
|
|
//| SubsetSize<0; |
|
|
//| Subset - subset of SubsetSize elements, array[SubsetSize]; |
|
|
//| SubsetSize- number of elements in Subset[] array: |
|
|
//| * if SubsetSize>0, rows of XY with indices |
|
|
//| Subset[0]......Subset[SubsetSize-1] are processed|
|
|
//| * if SubsetSize=0, zeros are returned |
|
|
//| * if SubsetSize<0, entire dataset is processed; |
|
|
//| Subset[] array is ignored in this case. |
|
|
//| RESULT: |
|
|
//| sum-of-squares error, SUM(sqr(y[i]-desired_y[i])/2) |
|
|
//| DATASET FORMAT: |
|
|
//| This function uses two different dataset formats - one for |
|
|
//| regression networks, another one for classification networks. |
|
|
//| For regression networks with NIn inputs and NOut outputs |
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+NOut) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, next NOut columns are |
|
|
//| outputs |
|
|
//| For classification networks with NIn inputs and NClasses clases|
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+1) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, last column stores class |
|
|
//| number (from 0 to NClasses-1). |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPBase::MLPErrorSparseSubset(CMultilayerPerceptron &network,
|
|
CSparseMatrix &xy,int setsize,
|
|
CRowInt &subset,int subsetsize)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int idx0=0;
|
|
int idx1=0;
|
|
int idxtype=0;
|
|
//--- check
|
|
if(!CAp::Assert(CSparse::SparseIsCRS(xy),__FUNCTION__": XY is not in CRS format."))
|
|
return(0);
|
|
if(!CAp::Assert(CSparse::SparseGetNRows(xy)>=setsize,__FUNCTION__": XY has less than SetSize rows"))
|
|
return(0);
|
|
if(setsize>0)
|
|
{
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+1,__FUNCTION__": XY has less than NIn+1 columns"))
|
|
return(0);
|
|
}
|
|
else
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(xy)>=MLPGetInputsCount(network)+MLPGetOutputsCount(network),__FUNCTION__": XY has less than NIn+NOut columns"))
|
|
return(0);
|
|
}
|
|
if(subsetsize>=0)
|
|
{
|
|
idx0=0;
|
|
idx1=subsetsize;
|
|
idxtype=1;
|
|
}
|
|
else
|
|
{
|
|
idx0=0;
|
|
idx1=setsize;
|
|
idxtype=0;
|
|
}
|
|
//--- function call
|
|
MLPAllErrorsX(network,network.m_dummydxy,xy,setsize,1,subset,idx0,idx1,idxtype,network.m_err);
|
|
result=CMath::Sqr(network.m_err.m_RMSError)*(idx1-idx0)*MLPGetOutputsCount(network)/2;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Calculation of all types of errors at once for a subset or full |
|
|
//| dataset, which can be represented in different formats. |
|
|
//| THIS INTERNAL FUNCTION IS NOT INTENDED TO BE USED BY ALGLIB USERS|
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPAllErrorsX(CMultilayerPerceptron &network,
|
|
CMatrixDouble &densexy,
|
|
CSparseMatrix &sparsexy,
|
|
int datasetsize,
|
|
int datasettype,
|
|
CRowInt &idx,
|
|
int subset0,
|
|
int subset1,
|
|
int subsettype,
|
|
CModelErrors &rep)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int rowsize=0;
|
|
bool iscls;
|
|
int srcidx=0;
|
|
int cstart=0;
|
|
int csize=0;
|
|
int len0=0;
|
|
int len1=0;
|
|
double problemcost=0;
|
|
CMLPBuffers pbuf;
|
|
CModelErrors rep0;
|
|
CModelErrors rep1;
|
|
//--- check
|
|
if(!CAp::Assert(datasetsize>=0,__FUNCTION__": SetSize<0"))
|
|
return;
|
|
if(!CAp::Assert(datasettype==0 || datasettype==1,__FUNCTION__": DatasetType is incorrect"))
|
|
return;
|
|
if(!CAp::Assert(subsettype==0 || subsettype==1,__FUNCTION__": SubsetType is incorrect"))
|
|
return;
|
|
//--- Determine network properties
|
|
MLPProperties(network,nin,nout,wcount);
|
|
iscls=MLPIsSoftMax(network);
|
|
//--- Retrieve and prepare
|
|
if(iscls)
|
|
{
|
|
rowsize=nin+1;
|
|
CBdSS::DSErrAllocate(nout,pbuf.m_Tmp0);
|
|
}
|
|
else
|
|
{
|
|
rowsize=nin+nout;
|
|
CBdSS::DSErrAllocate(-nout,pbuf.m_Tmp0);
|
|
}
|
|
//--- Processing
|
|
CHPCCores::HPCPrepareChunkedGradient(network.m_weights,wcount,MLPNTotal(network),nin,nout,pbuf);
|
|
cstart=subset0;
|
|
while(cstart<subset1)
|
|
{
|
|
//--- Determine size of current chunk and copy it to PBuf.XY
|
|
csize=MathMin(subset1,cstart+pbuf.m_ChunkSize)-cstart;
|
|
for(int j=0; j<csize; j++)
|
|
{
|
|
srcidx=-1;
|
|
if(subsettype==0)
|
|
srcidx=cstart+j;
|
|
else
|
|
if(subsettype==1)
|
|
srcidx=idx[cstart+j];
|
|
if(!CAp::Assert(srcidx>=0,__FUNCTION__": internal error"))
|
|
return;
|
|
if(datasettype==0)
|
|
pbuf.m_XY.Row(j,densexy,srcidx);
|
|
else
|
|
if(datasettype==1)
|
|
{
|
|
CSparse::SparseGetRow(sparsexy,srcidx,pbuf.m_XYRow);
|
|
pbuf.m_XY.Row(j,pbuf.m_XYRow);
|
|
}
|
|
}
|
|
//--- Unpack XY and process (temporary code, to be replaced by chunked processing)
|
|
pbuf.m_XY2=pbuf.m_XY;
|
|
MLPChunkedProcess(network,pbuf.m_XY2,0,csize,pbuf.m_Batch4Buf,pbuf.m_HPCBuf);
|
|
ulong parts[]={nin,nout};
|
|
matrix<double> splitted[];
|
|
matrix<double> splitted2[];
|
|
//--- Split
|
|
pbuf.m_XY2.Split(parts,1,splitted2);
|
|
if(iscls)
|
|
parts[1]=1;
|
|
pbuf.m_XY.Split(parts,1,splitted);
|
|
for(int j=0; j<csize; j++)
|
|
{
|
|
pbuf.m_X=splitted2[0].Row(j);
|
|
pbuf.m_Y=splitted2[1].Row(j);
|
|
pbuf.m_Desiredy=splitted[1].Row(j);
|
|
CBdSS::DSErrAccumulate(pbuf.m_Tmp0,pbuf.m_Y,pbuf.m_Desiredy);
|
|
}
|
|
//--- Process chunk and advance line pointer
|
|
cstart+=pbuf.m_ChunkSize;
|
|
}
|
|
CBdSS::DSErrFinish(pbuf.m_Tmp0);
|
|
rep.m_RelCLSError=pbuf.m_Tmp0[0];
|
|
rep.m_AvgCE=pbuf.m_Tmp0[1]/MathLog(2);
|
|
rep.m_RMSError=pbuf.m_Tmp0[2];
|
|
rep.m_AvgError=pbuf.m_Tmp0[3];
|
|
rep.m_AvgRelError=pbuf.m_Tmp0[4];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine: adding new input layer to network |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::AddInputLayer(const int ncount,CRowInt &lsizes,
|
|
CRowInt <ypes,CRowInt &lconnfirst,
|
|
CRowInt &lconnlast,int &lastproc)
|
|
{
|
|
//--- change values
|
|
lsizes.Set(0,ncount);
|
|
ltypes.Set(0,-2);
|
|
lconnfirst.Set(0,0);
|
|
lconnlast.Set(0,0);
|
|
lastproc=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine: adding new summator layer to network |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::AddBiasedSummatorLayer(const int ncount,CRowInt &lsizes,
|
|
CRowInt <ypes,CRowInt &lconnfirst,
|
|
CRowInt &lconnlast,int &lastproc)
|
|
{
|
|
//--- change values
|
|
lsizes.Set(lastproc+1,1);
|
|
ltypes.Set(lastproc+1,-3);
|
|
lconnfirst.Set(lastproc+1,0);
|
|
lconnlast.Set(lastproc+1,0);
|
|
lsizes.Set(lastproc+2,ncount);
|
|
ltypes.Set(lastproc+2,0);
|
|
lconnfirst.Set(lastproc+2,lastproc);
|
|
lconnlast.Set(lastproc+2,lastproc+1);
|
|
lastproc=lastproc+2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine: adding new summator layer to network |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::AddActivationLayer(const int functype,CRowInt &lsizes,
|
|
CRowInt <ypes,CRowInt &lconnfirst,
|
|
CRowInt &lconnlast,int &lastproc)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(functype>0 || functype==-5,__FUNCTION__+": incorrect function type"))
|
|
return;
|
|
//--- change values
|
|
lsizes.Set(lastproc+1,lsizes[lastproc]);
|
|
ltypes.Set(lastproc+1,functype);
|
|
lconnfirst.Set(lastproc+1,lastproc);
|
|
lconnlast.Set(lastproc+1,lastproc);
|
|
lastproc=lastproc+1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine: adding new zero layer to network |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::AddZeroLayer(CRowInt &lsizes,CRowInt <ypes,
|
|
CRowInt &lconnfirst,CRowInt &lconnlast,
|
|
int &lastproc)
|
|
{
|
|
//--- change values
|
|
lsizes.Set(lastproc+1,1);
|
|
ltypes.Set(lastproc+1,-4);
|
|
lconnfirst.Set(lastproc+1,0);
|
|
lconnlast.Set(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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::HLAddInputLayer(CMultilayerPerceptron &network,
|
|
int &connidx,int &neuroidx,
|
|
int &structinfoidx,int nin)
|
|
{
|
|
//--- initialization
|
|
int offs=m_hlm_nfieldwidth*neuroidx;
|
|
//--- change values
|
|
for(int i=0; i<nin; i++)
|
|
{
|
|
network.m_hlneurons.Set(offs,0);
|
|
network.m_hlneurons.Set(offs+1,i);
|
|
network.m_hlneurons.Set(offs+2,-1);
|
|
network.m_hlneurons.Set(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|
|
|
//+------------------------------------------------------------------+
|
|
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; i++)
|
|
{
|
|
//--- change values
|
|
network.m_hlneurons.Set(neurooffs,k);
|
|
network.m_hlneurons.Set(neurooffs+1,i);
|
|
network.m_hlneurons.Set(neurooffs+2,structinfoidx+1+nout+i);
|
|
network.m_hlneurons.Set(neurooffs+3,weightsidx+nprev+(nprev+1)*i);
|
|
neurooffs=neurooffs+m_hlm_nfieldwidth;
|
|
}
|
|
for(i=0; i<nprev; i++)
|
|
{
|
|
for(j=0; j<nout; j++)
|
|
{
|
|
//--- change values
|
|
network.m_hlconnections.Set(connoffs,k-1);
|
|
network.m_hlconnections.Set(connoffs+1,i);
|
|
network.m_hlconnections.Set(connoffs+2,k);
|
|
network.m_hlconnections.Set(connoffs+3,j);
|
|
network.m_hlconnections.Set(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-1; i++)
|
|
{
|
|
//--- change values
|
|
network.m_hlneurons.Set(neurooffs,k);
|
|
network.m_hlneurons.Set(neurooffs+1,i);
|
|
network.m_hlneurons.Set(neurooffs+2,-1);
|
|
network.m_hlneurons.Set(neurooffs+3,weightsidx+nprev+(nprev+1)*i);
|
|
neurooffs=neurooffs+m_hlm_nfieldwidth;
|
|
}
|
|
//--- change values
|
|
network.m_hlneurons.Set(neurooffs,k);
|
|
network.m_hlneurons.Set(neurooffs+1,i);
|
|
network.m_hlneurons.Set(neurooffs+2,-1);
|
|
network.m_hlneurons.Set(neurooffs+3,-1);
|
|
for(i=0; i<nprev; i++)
|
|
{
|
|
for(j=0; j<nout-1; j++)
|
|
{
|
|
//--- change values
|
|
network.m_hlconnections.Set(connoffs,k-1);
|
|
network.m_hlconnections.Set(connoffs+1,i);
|
|
network.m_hlconnections.Set(connoffs+2,k);
|
|
network.m_hlconnections.Set(connoffs+3,j);
|
|
network.m_hlconnections.Set(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|
|
|
//+------------------------------------------------------------------+
|
|
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 neurooffs=m_hlm_nfieldwidth*neuroidx;
|
|
int connoffs=m_hlconm_nfieldwidth*connidx;
|
|
|
|
for(int i=0; i<ncur; i++)
|
|
{
|
|
//--- change values
|
|
network.m_hlneurons.Set(neurooffs,k);
|
|
network.m_hlneurons.Set(neurooffs+1,i);
|
|
network.m_hlneurons.Set(neurooffs+2,structinfoidx+1+ncur+i);
|
|
network.m_hlneurons.Set(neurooffs+3,weightsidx+nprev+(nprev+1)*i);
|
|
neurooffs=neurooffs+m_hlm_nfieldwidth;
|
|
}
|
|
for(int i=0; i<nprev; i++)
|
|
{
|
|
for(int j=0; j<ncur; j++)
|
|
{
|
|
//--- change values
|
|
network.m_hlconnections.Set(connoffs,k-1);
|
|
network.m_hlconnections.Set(connoffs+1,i);
|
|
network.m_hlconnections.Set(connoffs+2,k);
|
|
network.m_hlconnections.Set(connoffs+3,j);
|
|
network.m_hlconnections.Set(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 |
|
|
//+------------------------------------------------------------------+
|
|
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
|
|
network.m_hlnetworktype=0;
|
|
//--- network without hidden layers
|
|
if(nhid1==0)
|
|
{
|
|
//--- allocation
|
|
network.m_hllayersizes.Resize(2);
|
|
//--- change values
|
|
network.m_hllayersizes.Set(0,nin);
|
|
network.m_hllayersizes.Set(1,nout);
|
|
//--- check
|
|
if(!iscls)
|
|
{
|
|
//--- allocation
|
|
network.m_hlconnections.Resize(m_hlconm_nfieldwidth*nin*nout);
|
|
network.m_hlneurons.Resize(m_hlm_nfieldwidth*(nin+nout));
|
|
network.m_hlnormtype=0;
|
|
}
|
|
else
|
|
{
|
|
//--- allocation
|
|
network.m_hlconnections.Resize(m_hlconm_nfieldwidth*nin*(nout-1));
|
|
network.m_hlneurons.Resize(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
|
|
network.m_hllayersizes.Resize(3);
|
|
//--- change values
|
|
network.m_hllayersizes.Set(0,nin);
|
|
network.m_hllayersizes.Set(1,nhid1);
|
|
network.m_hllayersizes.Set(2,nout);
|
|
//--- check
|
|
if(!iscls)
|
|
{
|
|
//--- allocation
|
|
network.m_hlconnections.Resize(m_hlconm_nfieldwidth*(nin*nhid1+nhid1*nout));
|
|
network.m_hlneurons.Resize(m_hlm_nfieldwidth*(nin+nhid1+nout));
|
|
network.m_hlnormtype=0;
|
|
}
|
|
else
|
|
{
|
|
//--- allocation
|
|
network.m_hlconnections.Resize(m_hlconm_nfieldwidth*(nin*nhid1+nhid1*(nout-1)));
|
|
network.m_hlneurons.Resize(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
|
|
network.m_hllayersizes.Resize(4);
|
|
//--- change values
|
|
network.m_hllayersizes.Set(0,nin);
|
|
network.m_hllayersizes.Set(1,nhid1);
|
|
network.m_hllayersizes.Set(2,nhid2);
|
|
network.m_hllayersizes.Set(3,nout);
|
|
//--- check
|
|
if(!iscls)
|
|
{
|
|
//--- allocation
|
|
network.m_hlconnections.Resize(m_hlconm_nfieldwidth*(nin*nhid1+nhid1*nhid2+nhid2*nout));
|
|
network.m_hlneurons.Resize(m_hlm_nfieldwidth*(nin+nhid1+nhid2+nout));
|
|
network.m_hlnormtype=0;
|
|
}
|
|
else
|
|
{
|
|
//--- allocation
|
|
network.m_hlconnections.Resize(m_hlconm_nfieldwidth*(nin*nhid1+nhid1*nhid2+nhid2*(nout-1)));
|
|
network.m_hlneurons.Resize(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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPCreate(const int nin,const int nout,CRowInt &lsizes,
|
|
CRowInt <ypes,CRowInt &lconnfirst,CRowInt &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[];
|
|
CMLPBuffers buf;
|
|
CSMLPGrad sgrad;
|
|
//--- 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; 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; 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]+=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+=lsizes[i];
|
|
//--- check
|
|
if(ltypes[i]==0)
|
|
wcount+=lnsyn[i]*lsizes[i];
|
|
}
|
|
ssize=7+ntotal*m_nfieldwidth;
|
|
//--- Allocate
|
|
network.m_structinfo.Resize(ssize);
|
|
network.m_weights.Resize(wcount);
|
|
//--- check
|
|
if(isclsnet)
|
|
{
|
|
//--- allocation
|
|
network.m_columnmeans.Resize(nin);
|
|
network.m_columnsigmas.Resize(nin);
|
|
}
|
|
else
|
|
{
|
|
//--- allocation
|
|
network.m_columnmeans.Resize(nin+nout);
|
|
network.m_columnsigmas.Resize(nin+nout);
|
|
}
|
|
//--- allocation
|
|
network.m_neurons.Resize(ntotal);
|
|
network.m_nwbuf.Resize(MathMax(wcount,2*nout));
|
|
network.m_integerbuf.Resize(4);
|
|
network.m_dfdnet.Resize(ntotal);
|
|
network.m_x.Resize(nin);
|
|
network.m_y.Resize(nout);
|
|
network.m_derror.Resize(ntotal);
|
|
//--- Fill structure:
|
|
//--- * first, fill by dummy values to avoid spurious reports by Valgrind
|
|
//--- * then fill global info header
|
|
network.m_structinfo.Fill(-999999);
|
|
network.m_structinfo.Set(0,ssize);
|
|
network.m_structinfo.Set(1,nin);
|
|
network.m_structinfo.Set(2,nout);
|
|
network.m_structinfo.Set(3,ntotal);
|
|
network.m_structinfo.Set(4,wcount);
|
|
network.m_structinfo.Set(5,7);
|
|
//--- check
|
|
if(isclsnet)
|
|
network.m_structinfo.Set(6,1);
|
|
else
|
|
network.m_structinfo.Set(6,0);
|
|
//--- Fill structure: neuron connections
|
|
nprocessed=0;
|
|
wallocated=0;
|
|
//--- calculation
|
|
for(i=0; i<layerscount; i++)
|
|
{
|
|
for(j=0; j<lsizes[i]; j++)
|
|
{
|
|
offs=network.m_structinfo[5]+nprocessed*m_nfieldwidth;
|
|
network.m_structinfo.Set(offs,ltypes[i]);
|
|
//--- check
|
|
if(ltypes[i]==0)
|
|
{
|
|
//--- Adaptive summator:
|
|
//--- * connections with weights to previous neurons
|
|
network.m_structinfo.Set(offs+1,lnsyn[i]);
|
|
network.m_structinfo.Set(offs+2,lnfirst[lconnfirst[i]]);
|
|
network.m_structinfo.Set(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.Set(offs+1,1);
|
|
network.m_structinfo.Set(offs+2,lnfirst[lconnfirst[i]]+j);
|
|
network.m_structinfo.Set(offs+3,-1);
|
|
nprocessed=nprocessed+1;
|
|
}
|
|
//--- check
|
|
if((ltypes[i]==-2 || ltypes[i]==-3) || ltypes[i]==-4)
|
|
nprocessed++;
|
|
}
|
|
}
|
|
//--- 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
|
|
network.m_columnmeans.Fill(0);
|
|
network.m_columnsigmas.Fill(1);
|
|
MLPRandomize(network);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine for Hessian calculation. |
|
|
//| WARNING!!! Unspeakable math far beyong human capabilities :) |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPHessianBatchInternal(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int ssize,
|
|
const bool naturalerr,double &e,
|
|
CRowDouble &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
|
|
CRowDouble x;
|
|
CRowDouble desiredy;
|
|
CRowDouble gt;
|
|
//--- create matrix
|
|
CMatrixDouble rx;
|
|
CMatrixDouble ry;
|
|
CMatrixDouble rdx;
|
|
CMatrixDouble rdy;
|
|
matrix<double> splittedXY[];
|
|
//--- initialization
|
|
e=0;
|
|
//--- function call
|
|
MLPProperties(network,nin,nout,wcount);
|
|
//--- initialization
|
|
ntotal=network.m_structinfo[3];
|
|
istart=network.m_structinfo[5];
|
|
//--- Prepare
|
|
rdx.Resize(ntotal+nout,wcount);
|
|
rdy.Resize(ntotal+nout,wcount);
|
|
//--- initialization
|
|
e=0;
|
|
grad=vector<double>::Zeros(wcount);
|
|
h=matrix<double>::Zeros(wcount,wcount);
|
|
ulong parts[2];
|
|
parts[0]=nin;
|
|
parts[1]=(MLPIsSoftMax(network)?1:nout);
|
|
xy.Split(parts,1,splittedXY);
|
|
//--- Process
|
|
for(k=0; k<ssize ; k++)
|
|
{
|
|
//--- Process vector with MLPGradN.
|
|
//--- Now Neurons,DFDNET and DError contains results of the last run.
|
|
x=splittedXY[0].Row(k);
|
|
if(MLPIsSoftMax(network))
|
|
{
|
|
kl=(int)MathRound(splittedXY[1][k,0]);
|
|
desiredy=vector<double>::Zeros(nout);
|
|
desiredy.Set(kl,1);
|
|
}
|
|
else
|
|
desiredy=splittedXY[1].Row(k);
|
|
//--- check
|
|
if(naturalerr)
|
|
MLPGradN(network,x,desiredy,et,gt);
|
|
else
|
|
MLPGrad(network,x,desiredy,et,gt);
|
|
//--- grad,error
|
|
e+=et;
|
|
grad+=gt;
|
|
//--- Hessian.
|
|
//--- Forward pass of the R-algorithm
|
|
rx=matrix<double>::Zeros(ntotal,wcount);
|
|
ry=rx;
|
|
for(i=0; i<ntotal; i++)
|
|
{
|
|
offs=istart+i*m_nfieldwidth;
|
|
//--- check
|
|
if(network.m_structinfo[offs]>0 || network.m_structinfo[offs]==-5)
|
|
{
|
|
//--- Activation function
|
|
n1=network.m_structinfo[offs+2];
|
|
rx.Row(i,ry,n1);
|
|
//--- calculation
|
|
v=network.m_dfdnet[i];
|
|
ry.Row(i,rx[i]*v);
|
|
continue;
|
|
}
|
|
//--- check
|
|
if(network.m_structinfo[offs]==0)
|
|
{
|
|
//--- Adaptive summator
|
|
n1=network.m_structinfo[offs+2];
|
|
n2=n1+network.m_structinfo[offs+1];
|
|
w1=network.m_structinfo[offs+3];
|
|
w2=w1+network.m_structinfo[offs+1];
|
|
//--- calculation
|
|
for(j=n1; j<n2; j++)
|
|
{
|
|
v=network.m_weights[w1+j-n1];
|
|
rx.Row(i,rx[i]+ry[j]*v);
|
|
rx.Add(i,w1+j-n1,network.m_neurons[j]);
|
|
}
|
|
ry.Row(i,rx,i);
|
|
continue;
|
|
}
|
|
//--- check
|
|
if(network.m_structinfo[offs]<0)
|
|
{
|
|
bflag=true;
|
|
//--- check
|
|
switch(network.m_structinfo[offs])
|
|
{
|
|
case -2:
|
|
//--- input neuron,left unchanged
|
|
bflag=false;
|
|
break;
|
|
case -3:
|
|
//--- "-1" neuron,left unchanged
|
|
bflag=false;
|
|
break;
|
|
case -4:
|
|
//--- "0" neuron,left unchanged
|
|
bflag=false;
|
|
break;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(!bflag,__FUNCTION__+": internal error - unknown neuron type!"))
|
|
return;
|
|
continue;
|
|
}
|
|
}
|
|
//--- Hessian. Backward pass of the R-algorithm.
|
|
//--- Stage 1. Initialize RDY
|
|
rdy=matrix<double>::Zeros(ntotal+nout,wcount);
|
|
rx.Resize(ntotal+nout,wcount);
|
|
ry.Resize(ntotal+nout,wcount);
|
|
//--- 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; i++)
|
|
{
|
|
n1=ntotal-nout+i;
|
|
n2=ntotal+i;
|
|
//--- Forward pass from N1 to N2
|
|
rx.Row(n2,ry,n1);
|
|
v=network.m_columnsigmas[nin+i];
|
|
ry.Row(n2,rx[n2]*v);
|
|
//--- Initialization of RDY
|
|
rdy.Row(n2,ry,n2);
|
|
//--- 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];
|
|
rdx.Row(n2,rdy[n2]*df);
|
|
rdy.Row(n1,rdy[n1]+rdx[n2]);
|
|
}
|
|
}
|
|
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=desiredy.Sum();
|
|
mx=network.m_neurons[ntotal-nout];
|
|
//--- calculation
|
|
for(i=1; i<nout; i++)
|
|
mx=MathMax(mx,network.m_neurons[ntotal-nout+i]);
|
|
s=0;
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
network.m_nwbuf.Set(i,MathExp(network.m_neurons[ntotal-nout+i]-mx));
|
|
s=s+network.m_nwbuf[i];
|
|
}
|
|
//--- calculation
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
for(j=0; j<nout; j++)
|
|
{
|
|
//--- check
|
|
if(j==i)
|
|
{
|
|
deidyj=t*network.m_nwbuf[i]*(s-network.m_nwbuf[i])/CMath::Sqr(s);
|
|
rdy.Row(ntotal-nout+i,rdy[ntotal-nout+i]+ry[ntotal-nout+i]*deidyj);
|
|
}
|
|
else
|
|
{
|
|
deidyj=-(t*network.m_nwbuf[i]*network.m_nwbuf[j]/CMath::Sqr(s));
|
|
rdy.Row(ntotal-nout+i,rdy[ntotal-nout+i]+ry[ntotal-nout+j]*deidyj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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=1; i<nout; i++)
|
|
mx=MathMax(mx,network.m_neurons[ntotal-nout+i]);
|
|
//--- calculation
|
|
s=0;
|
|
s2=0;
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
network.m_nwbuf.Set(i,MathExp(network.m_neurons[ntotal-nout+i]-mx));
|
|
s+=network.m_nwbuf[i];
|
|
s2+=CMath::Sqr(network.m_nwbuf[i]);
|
|
}
|
|
//--- calculation
|
|
q=0;
|
|
for(i=0; i<nout; i++)
|
|
q+=(network.m_y[i]-desiredy[i])*network.m_nwbuf[i];
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
//--- change values
|
|
z=-q+(network.m_y[i]-desiredy[i])*s;
|
|
expi=network.m_nwbuf[i];
|
|
for(j=0; j<nout; 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]));
|
|
rdy.Row(ntotal-nout+i,rdy[ntotal-nout+i]+ry[ntotal-nout+j]*deidyj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--- 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 || network.m_structinfo[offs]==-5)
|
|
{
|
|
n1=network.m_structinfo[offs+2];
|
|
//--- First,calculate R(dE/dX).
|
|
MLPActivationFunction(network.m_neurons[n1],network.m_structinfo[offs],f,df,d2f);
|
|
v=d2f*network.m_derror[i];
|
|
rdx.Row(i,rdy[i]*df);
|
|
rdx.Row(i,rdx[i]+rx[i]*v);
|
|
//--- 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)
|
|
rdy.Row(n1,rdy[n1]+rdx[i]);
|
|
continue;
|
|
}
|
|
//--- check
|
|
if(network.m_structinfo[offs]==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).
|
|
rdx.Row(i,rdy,i);
|
|
//--- Then,calculate R(dE/dWij)
|
|
for(j=w1; j<=w2; j++)
|
|
{
|
|
v=network.m_neurons[n1+j-w1];
|
|
h.Row(j,h[j]+rdx[i]*v);
|
|
//--- calculation
|
|
v=network.m_derror[i];
|
|
h.Row(j,h[j]+ry[n1+j-w1]*v);
|
|
}
|
|
//--- And finally,update R(dE/dY) for connected neurons.
|
|
for(j=w1; j<=w2; j++)
|
|
{
|
|
v=network.m_weights[j];
|
|
rdy.Row(n1+j-w1,rdy[n1+j-w1]+ rdx[i]*v);
|
|
rdy.Set(n1+j-w1,j,rdy[n1+j-w1][j]+network.m_derror[i]);
|
|
}
|
|
continue;
|
|
}
|
|
//--- check
|
|
if(network.m_structinfo[offs]<0)
|
|
{
|
|
bflag=false;
|
|
//--- check
|
|
if((network.m_structinfo[offs]==-2 || network.m_structinfo[offs]==-3) || network.m_structinfo[offs]==-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 |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPInternalCalculateGradient(CMultilayerPerceptron &network,
|
|
CRowDouble &neurons,
|
|
CRowDouble &weights,
|
|
CRowDouble &derror,
|
|
CRowDouble &grad,
|
|
const bool naturalerrorfunc)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int n1=0;
|
|
int n2=0;
|
|
int w1=0;
|
|
int w2=0;
|
|
int ntotal=network.m_structinfo[3];
|
|
int istart=network.m_structinfo[5];
|
|
int nin=network.m_structinfo[1];
|
|
int nout=network.m_structinfo[2];
|
|
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;
|
|
//--- 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=1; i<nout; i++)
|
|
mx=MathMax(mx,network.m_neurons[ntotal-nout+i]);
|
|
net=0;
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
network.m_nwbuf.Set(i,MathExp(network.m_neurons[ntotal-nout+i]-mx));
|
|
net+=network.m_nwbuf[i];
|
|
}
|
|
//--- calculation
|
|
i1_=ntotal-nout;
|
|
v=0.0;
|
|
for(i=0; i<nout; i++)
|
|
v+=network.m_derror[i+i1_]*network.m_nwbuf[i];
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
fown=network.m_nwbuf[i];
|
|
deown=network.m_derror[i+i1_];
|
|
network.m_nwbuf.Set(nout+i,(-v+deown*fown+deown*(net-fown))*fown/CMath::Sqr(net));
|
|
network.m_derror.Set(i+i1_,network.m_nwbuf[nout+i]);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Un-standardisation
|
|
for(i=0; i<nout; i++)
|
|
network.m_derror.Set(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 || network.m_structinfo[offs]==-5)
|
|
{
|
|
//--- Activation function
|
|
dedf=network.m_derror[i];
|
|
dfdnet=network.m_dfdnet[i];
|
|
derror.Set(network.m_structinfo[offs+2],derror[network.m_structinfo[offs+2]]+dedf*dfdnet);
|
|
continue;
|
|
}
|
|
//--- check
|
|
if(network.m_structinfo[offs]==0)
|
|
{
|
|
//--- Adaptive summator
|
|
n1=network.m_structinfo[offs+2];
|
|
n2=n1+network.m_structinfo[offs+1];
|
|
w1=network.m_structinfo[offs+3];
|
|
w2=w1+network.m_structinfo[offs+1];
|
|
dedf=network.m_derror[i];
|
|
dfdnet=1.0;
|
|
v=dedf*dfdnet;
|
|
i1_=n1-w1;
|
|
//--- calculation
|
|
for(i_=w1; i_<w2; i_++)
|
|
grad.Set(i_,v*neurons[i_+i1_]);
|
|
i1_=w1-n1;
|
|
for(i_=n1; i_<n2; i_++)
|
|
derror.Add(i_,v*weights[i_+i1_]);
|
|
continue;
|
|
}
|
|
//--- check
|
|
if(network.m_structinfo[offs]<0)
|
|
{
|
|
bflag=false;
|
|
//--- check
|
|
if((network.m_structinfo[offs]==-2 || network.m_structinfo[offs]==-3) || network.m_structinfo[offs]==-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 |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPChunkedGradient(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,const int cstart,
|
|
const int csize,CRowDouble &batch4buf,
|
|
CRowDouble &hpcbuf,
|
|
double &e,const bool naturalerrorfunc)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int kl=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 vv=0;
|
|
double s=0;
|
|
double fown=0;
|
|
double deown=0;
|
|
bool bflag;
|
|
int istart=0;
|
|
int entrysize=0;
|
|
int dfoffs=0;
|
|
int derroroffs=0;
|
|
int entryoffs=0;
|
|
int neuronidx=0;
|
|
int srcentryoffs=0;
|
|
int srcneuronidx=0;
|
|
int srcweightidx=0;
|
|
int neurontype=0;
|
|
int nweights=0;
|
|
int offs0=0;
|
|
int offs1=0;
|
|
int offs2=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double v2=0;
|
|
double v3=0;
|
|
double s0=0;
|
|
double s1=0;
|
|
double s2=0;
|
|
double s3=0;
|
|
int chunksize=4;
|
|
//--- check
|
|
if(!CAp::Assert(csize<=chunksize,__FUNCTION__": internal error (CSize>ChunkSize)"))
|
|
return;
|
|
//--- 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];
|
|
entrysize=12;
|
|
dfoffs=4;
|
|
derroroffs=8;
|
|
//--- Fill Batch4Buf by zeros.
|
|
//--- THIS STAGE IS VERY IMPORTANT!
|
|
//--- We fill all components of entry - neuron values, dF/dNET, dError/dF.
|
|
//--- It allows us to easily handle situations when CSize<ChunkSize by
|
|
//--- simply working with ALL components of Batch4Buf, without ever
|
|
//--- looking at CSize. The idea is that dError/dF for absent components
|
|
//--- will be initialized by zeros - and won't be rewritten by non-zero
|
|
//--- values during backpropagation.
|
|
batch4buf.Fill(0);
|
|
//--- Forward pass:
|
|
//--- 1. Load data into Batch4Buf. If CSize<ChunkSize, data are padded by zeros.
|
|
//--- 2. Perform forward pass through network
|
|
for(i=0; i<nin; i++)
|
|
{
|
|
entryoffs=entrysize*i;
|
|
for(j=0; j<csize; j++)
|
|
{
|
|
if(network.m_columnsigmas[i]!=0.0)
|
|
batch4buf.Set(entryoffs+j,(xy.Get(cstart+j,i)-network.m_columnmeans[i])/network.m_columnsigmas[i]);
|
|
else
|
|
batch4buf.Set(entryoffs+j,xy.Get(cstart+j,i)-network.m_columnmeans[i]);
|
|
}
|
|
}
|
|
for(neuronidx=0; neuronidx<ntotal; neuronidx++)
|
|
{
|
|
entryoffs=entrysize*neuronidx;
|
|
offs=istart+neuronidx*m_nfieldwidth;
|
|
neurontype=network.m_structinfo[offs+0];
|
|
if(neurontype>0 || neurontype==-5)
|
|
{
|
|
//--- "activation function" neuron, which takes value of neuron SrcNeuronIdx
|
|
//--- and applies activation function to it.
|
|
//--- This neuron has no weights and no tunable parameters.
|
|
srcneuronidx=network.m_structinfo[offs+2];
|
|
srcentryoffs=entrysize*srcneuronidx;
|
|
for(i=0; i<4; i++)
|
|
{
|
|
MLPActivationFunction(batch4buf[srcentryoffs+i],neurontype,f,df,d2f);
|
|
batch4buf.Set(entryoffs+i,f);
|
|
batch4buf.Set(entryoffs+dfoffs+i,df);
|
|
}
|
|
continue;
|
|
}
|
|
if(neurontype==0)
|
|
{
|
|
//--- "adaptive summator" neuron, whose output is a weighted sum of inputs.
|
|
//--- It has weights, but has no activation function.
|
|
nweights=network.m_structinfo[offs+1];
|
|
srcneuronidx=network.m_structinfo[offs+2];
|
|
srcentryoffs=entrysize*srcneuronidx;
|
|
srcweightidx=network.m_structinfo[offs+3];
|
|
v0=0;
|
|
v1=0;
|
|
v2=0;
|
|
v3=0;
|
|
for(j=0; j<nweights; j++)
|
|
{
|
|
v=network.m_weights[srcweightidx];
|
|
srcweightidx++;
|
|
v0+=v*batch4buf[srcentryoffs];
|
|
v1+=v*batch4buf[srcentryoffs+1];
|
|
v2+=v*batch4buf[srcentryoffs+2];
|
|
v3+=v*batch4buf[srcentryoffs+3];
|
|
srcentryoffs+=entrysize;
|
|
}
|
|
batch4buf.Set(entryoffs,v0);
|
|
batch4buf.Set(entryoffs+1,v1);
|
|
batch4buf.Set(entryoffs+2,v2);
|
|
batch4buf.Set(entryoffs+3,v3);
|
|
batch4buf.Set(entryoffs+dfoffs,1);
|
|
batch4buf.Set(entryoffs+1+dfoffs,1);
|
|
batch4buf.Set(entryoffs+2+dfoffs,1);
|
|
batch4buf.Set(entryoffs+3+dfoffs,1);
|
|
continue;
|
|
}
|
|
if(neurontype<0)
|
|
{
|
|
bflag=false;
|
|
switch(neurontype)
|
|
{
|
|
case -2:
|
|
//--- Input neuron, left unchanged
|
|
bflag=true;
|
|
break;
|
|
case -3:
|
|
//--- "-1" neuron
|
|
batch4buf.Set(entryoffs,-1);
|
|
batch4buf.Set(entryoffs+1,-1);
|
|
batch4buf.Set(entryoffs+2,-1);
|
|
batch4buf.Set(entryoffs+3,-1);
|
|
batch4buf.Set(entryoffs+dfoffs,0);
|
|
batch4buf.Set(entryoffs+1+dfoffs,0);
|
|
batch4buf.Set(entryoffs+2+dfoffs,0);
|
|
batch4buf.Set(entryoffs+3+dfoffs,0);
|
|
bflag=true;
|
|
break;
|
|
case -4:
|
|
//--- "0" neuron
|
|
batch4buf.Set(entryoffs,0);
|
|
batch4buf.Set(entryoffs+1,0);
|
|
batch4buf.Set(entryoffs+2,0);
|
|
batch4buf.Set(entryoffs+3,0);
|
|
batch4buf.Set(entryoffs+dfoffs,0);
|
|
batch4buf.Set(entryoffs+1+dfoffs,0);
|
|
batch4buf.Set(entryoffs+2+dfoffs,0);
|
|
batch4buf.Set(entryoffs+3+dfoffs,0);
|
|
bflag=true;
|
|
break;
|
|
}
|
|
if(!CAp::Assert(bflag,__FUNCTION__": internal error - unknown neuron type!"))
|
|
return;
|
|
continue;
|
|
}
|
|
}
|
|
//--- Intermediate phase between forward and backward passes.
|
|
//--- For regression networks:
|
|
//--- * forward pass is completely done (no additional post-processing is
|
|
//--- needed).
|
|
//--- * before starting backward pass, we have to calculate dError/dOut
|
|
//--- for output neurons. We also update error at this phase.
|
|
//--- For classification networks:
|
|
//--- * in addition to forward pass we apply SOFTMAX normalization to
|
|
//--- output neurons.
|
|
//--- * after applying normalization, we have to calculate dError/dOut,
|
|
//--- which is calculated in two steps:
|
|
//--- * first, we calculate derivative of error with respect to SOFTMAX
|
|
//--- normalized outputs (normalized dError)
|
|
//--- * then, we calculate derivative of error with respect to values
|
|
//--- of outputs BEFORE normalization was applied to them
|
|
if(!CAp::Assert(network.m_structinfo[6]==0 || network.m_structinfo[6]==1,__FUNCTION__": unknown normalization type!"))
|
|
return;
|
|
if(network.m_structinfo[6]==1)
|
|
{
|
|
//--- SOFTMAX-normalized network.
|
|
//--- First, calculate (V0,V1,V2,V3) - component-wise maximum
|
|
//--- of output neurons. This vector of maximum values will be
|
|
//--- used for normalization of outputs prior to calculating
|
|
//--- exponentials.
|
|
//--- NOTE: the only purpose of this stage is to prevent overflow
|
|
//--- during calculation of exponentials. With this stage
|
|
//--- we make sure that all exponentials are calculated
|
|
//--- with non-positive argument. If you load (0,0,0,0) to
|
|
//--- (V0,V1,V2,V3), your program will continue working -
|
|
//--- although with less robustness.
|
|
entryoffs=entrysize*(ntotal-nout);
|
|
v0=batch4buf[entryoffs];
|
|
v1=batch4buf[entryoffs+1];
|
|
v2=batch4buf[entryoffs+2];
|
|
v3=batch4buf[entryoffs+3];
|
|
entryoffs+=entrysize;
|
|
for(i=1; i<nout; i++)
|
|
{
|
|
v=batch4buf[entryoffs];
|
|
if(v>v0)
|
|
v0=v;
|
|
v=batch4buf[entryoffs+1];
|
|
if(v>v1)
|
|
v1=v;
|
|
v=batch4buf[entryoffs+2];
|
|
if(v>v2)
|
|
v2=v;
|
|
v=batch4buf[entryoffs+3];
|
|
if(v>v3)
|
|
v3=v;
|
|
entryoffs=entryoffs+entrysize;
|
|
}
|
|
//--- Then, calculate exponentials and place them to part of the
|
|
//--- array which is located past the last entry. We also
|
|
//--- calculate sum of exponentials which will be stored past the
|
|
//--- exponentials.
|
|
entryoffs=entrysize*(ntotal-nout);
|
|
offs0=entrysize*ntotal;
|
|
s0=0;
|
|
s1=0;
|
|
s2=0;
|
|
s3=0;
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
v=MathExp(batch4buf[entryoffs]-v0);
|
|
s0+=v;
|
|
batch4buf.Set(offs0,v);
|
|
v=MathExp(batch4buf[entryoffs+1]-v1);
|
|
s1+=v;
|
|
batch4buf.Set(offs0+1,v);
|
|
v=MathExp(batch4buf[entryoffs+2]-v2);
|
|
s2+=v;
|
|
batch4buf.Set(offs0+2,v);
|
|
v=MathExp(batch4buf[entryoffs+3]-v3);
|
|
s3+=v;
|
|
batch4buf.Set(offs0+3,v);
|
|
entryoffs=entryoffs+entrysize;
|
|
offs0=offs0+chunksize;
|
|
}
|
|
offs0=entrysize*ntotal+2*nout*chunksize;
|
|
batch4buf.Set(offs0,s0);
|
|
batch4buf.Set(offs0+1,s1);
|
|
batch4buf.Set(offs0+2,s2);
|
|
batch4buf.Set(offs0+3,s3);
|
|
//--- Now we have:
|
|
//--- * Batch4Buf[0...EntrySize*NTotal-1] stores:
|
|
//--- * NTotal*ChunkSize neuron output values (SOFTMAX normalization
|
|
//--- was not applied to these values),
|
|
//--- * NTotal*ChunkSize values of dF/dNET (derivative of neuron
|
|
//--- output with respect to its input)
|
|
//--- * NTotal*ChunkSize zeros in the elements which correspond to
|
|
//--- dError/dOut (derivative of error with respect to neuron output).
|
|
//--- * Batch4Buf[EntrySize*NTotal...EntrySize*NTotal+ChunkSize*NOut-1] -
|
|
//--- stores exponentials of last NOut neurons.
|
|
//--- * Batch4Buf[EntrySize*NTotal+ChunkSize*NOut-1...EntrySize*NTotal+ChunkSize*2*NOut-1]
|
|
//--- - can be used for temporary calculations
|
|
//--- * Batch4Buf[EntrySize*NTotal+ChunkSize*2*NOut...EntrySize*NTotal+ChunkSize*2*NOut+ChunkSize-1]
|
|
//--- - stores sum-of-exponentials
|
|
//--- Block below calculates derivatives of error function with respect
|
|
//--- to non-SOFTMAX-normalized output values of last NOut neurons.
|
|
//--- It is quite complicated; we do not describe algebra behind it,
|
|
//--- but if you want you may check it yourself :)
|
|
if(naturalerrorfunc)
|
|
{
|
|
//--- Calculate derivative of error with respect to values of
|
|
//--- output neurons PRIOR TO SOFTMAX NORMALIZATION. Because we
|
|
//--- use natural error function (cross-entropy), we can do so
|
|
//--- very easy.
|
|
offs0=entrysize*ntotal+2*nout*chunksize;
|
|
for(k=0; k<csize; k++)
|
|
{
|
|
s=batch4buf[offs0+k];
|
|
kl=(int)MathRound(xy.Get(cstart+k,nin));
|
|
offs1=(ntotal-nout)*entrysize+derroroffs+k;
|
|
offs2=entrysize*ntotal+k;
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
v=(double)(i==kl);
|
|
vv=batch4buf[offs2];
|
|
batch4buf.Set(offs1,vv/s-v);
|
|
e+=SafeCrossEntropy(v,vv/s);
|
|
offs1+=entrysize;
|
|
offs2+=chunksize;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- SOFTMAX normalization makes things very difficult.
|
|
//--- Sorry, we do not dare to describe this esoteric math
|
|
//--- in details.
|
|
offs0=entrysize*ntotal+chunksize*2*nout;
|
|
for(k=0; k<csize; k++)
|
|
{
|
|
s=batch4buf[offs0+k];
|
|
kl=(int)MathRound(xy.Get(cstart+k,nin));
|
|
vv=0;
|
|
offs1=entrysize*ntotal+k;
|
|
offs2=entrysize*ntotal+nout*chunksize+k;
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
fown=batch4buf[offs1];
|
|
if(i==kl)
|
|
deown=fown/s-1;
|
|
else
|
|
deown=fown/s;
|
|
batch4buf.Set(offs2,deown);
|
|
vv+=deown*fown;
|
|
e+=deown*deown/2;
|
|
offs1+=chunksize;
|
|
offs2+=chunksize;
|
|
}
|
|
offs1=entrysize*ntotal+k;
|
|
offs2=entrysize*ntotal+nout*chunksize+k;
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
fown=batch4buf[offs1];
|
|
deown=batch4buf[offs2];
|
|
batch4buf.Set((ntotal-nout+i)*entrysize+derroroffs+k,(-vv+deown*fown+deown*(s-fown))*fown/CMath::Sqr(s));
|
|
offs1+=chunksize;
|
|
offs2+=chunksize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Regression network with sum-of-squares function.
|
|
//--- For each NOut of last neurons:
|
|
//--- * calculate difference between actual and desired output
|
|
//--- * calculate dError/dOut for this neuron (proportional to difference)
|
|
//--- * store in in last 4 components of entry (these values are used
|
|
//--- to start backpropagation)
|
|
//--- * update error
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
v0=network.m_columnsigmas[nin+i];
|
|
v1=network.m_columnmeans[nin+i];
|
|
entryoffs=entrysize*(ntotal-nout+i);
|
|
offs0=entryoffs;
|
|
offs1=entryoffs+derroroffs;
|
|
for(j=0; j<csize; j++)
|
|
{
|
|
v=batch4buf[offs0+j]*v0+v1-xy.Get(cstart+j,nin+i);
|
|
batch4buf.Set(offs1+j,v*v0);
|
|
e+=v*v/2;
|
|
}
|
|
}
|
|
}
|
|
//--- Backpropagation
|
|
for(neuronidx=ntotal-1; neuronidx>=0; neuronidx--)
|
|
{
|
|
entryoffs=entrysize*neuronidx;
|
|
offs=istart+neuronidx*m_nfieldwidth;
|
|
neurontype=network.m_structinfo[offs+0];
|
|
if(neurontype>0 || neurontype==-5)
|
|
{
|
|
//--- Activation function
|
|
srcneuronidx=network.m_structinfo[offs+2];
|
|
srcentryoffs=entrysize*srcneuronidx;
|
|
offs0=srcentryoffs+derroroffs;
|
|
offs1=entryoffs+derroroffs;
|
|
offs2=entryoffs+dfoffs;
|
|
batch4buf.Set(offs0,batch4buf[offs0]+batch4buf[offs1]*batch4buf[offs2]);
|
|
batch4buf.Set(offs0+1,batch4buf[offs0+1]+batch4buf[offs1+1]*batch4buf[offs2+1]);
|
|
batch4buf.Set(offs0+2,batch4buf[offs0+2]+batch4buf[offs1+2]*batch4buf[offs2+2]);
|
|
batch4buf.Set(offs0+3,batch4buf[offs0+3]+batch4buf[offs1+3]*batch4buf[offs2+3]);
|
|
continue;
|
|
}
|
|
if(neurontype==0)
|
|
{
|
|
//--- Adaptive summator
|
|
nweights=network.m_structinfo[offs+1];
|
|
srcneuronidx=network.m_structinfo[offs+2];
|
|
srcentryoffs=entrysize*srcneuronidx;
|
|
srcweightidx=network.m_structinfo[offs+3];
|
|
v0=batch4buf[entryoffs+derroroffs];
|
|
v1=batch4buf[entryoffs+derroroffs+1];
|
|
v2=batch4buf[entryoffs+derroroffs+2];
|
|
v3=batch4buf[entryoffs+derroroffs+3];
|
|
for(j=0; j<nweights; j++)
|
|
{
|
|
offs0=srcentryoffs;
|
|
offs1=srcentryoffs+derroroffs;
|
|
v=network.m_weights[srcweightidx];
|
|
hpcbuf.Set(srcweightidx,hpcbuf[srcweightidx]+batch4buf[offs0]*v0+batch4buf[offs0+1]*v1+batch4buf[offs0+2]*v2+batch4buf[offs0+3]*v3);
|
|
batch4buf.Set(offs1,batch4buf[offs1+0]+v*v0);
|
|
batch4buf.Set(offs1+1,batch4buf[offs1+1]+v*v1);
|
|
batch4buf.Set(offs1+2,batch4buf[offs1+2]+v*v2);
|
|
batch4buf.Set(offs1+3,batch4buf[offs1+3]+v*v3);
|
|
srcentryoffs+=entrysize;
|
|
srcweightidx ++;
|
|
}
|
|
continue;
|
|
}
|
|
if(neurontype<0)
|
|
{
|
|
bflag=false;
|
|
if((neurontype==-2 || neurontype==-3) || neurontype==-4)
|
|
{
|
|
//--- Special neuron type, no back-propagation required
|
|
bflag=true;
|
|
}
|
|
if(!CAp::Assert(bflag,"MLPInternalCalculateGradient: unknown neuron type!"))
|
|
return;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::MLPChunkedProcess(CMultilayerPerceptron &network,
|
|
CMatrixDouble &xy,
|
|
int cstart,int csize,
|
|
CRowDouble &batch4buf,
|
|
CRowDouble &hpcbuf)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=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;
|
|
bool bflag;
|
|
int istart=0;
|
|
int entrysize=0;
|
|
int entryoffs=0;
|
|
int neuronidx=0;
|
|
int srcentryoffs=0;
|
|
int srcneuronidx=0;
|
|
int srcweightidx=0;
|
|
int neurontype=0;
|
|
int nweights=0;
|
|
int offs0=0;
|
|
double v0=0;
|
|
double v1=0;
|
|
double v2=0;
|
|
double v3=0;
|
|
double s0=0;
|
|
double s1=0;
|
|
double s2=0;
|
|
double s3=0;
|
|
int chunksize= 4;
|
|
//--- check
|
|
if(!CAp::Assert(csize<=chunksize,__FUNCTION__": internal error (CSize>ChunkSize)"))
|
|
return;
|
|
//--- 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];
|
|
entrysize=4;
|
|
//--- Fill Batch4Buf by zeros.
|
|
//--- THIS STAGE IS VERY IMPORTANT!
|
|
//--- We fill all components of entry - neuron values, dF/dNET, dError/dF.
|
|
//--- It allows us to easily handle situations when CSize<ChunkSize by
|
|
//--- simply working with ALL components of Batch4Buf, without ever
|
|
//--- looking at CSize.
|
|
for(i=0; i<entrysize*ntotal; i++)
|
|
batch4buf.Fill(0);
|
|
//--- Forward pass:
|
|
//--- 1. Load data into Batch4Buf. If CSize<ChunkSize, data are padded by zeros.
|
|
//--- 2. Perform forward pass through network
|
|
for(i=0; i<nin; i++)
|
|
{
|
|
entryoffs=entrysize*i;
|
|
for(j=0; j<csize; j++)
|
|
{
|
|
if((double)(network.m_columnsigmas[i])!=0.0)
|
|
batch4buf.Set(entryoffs+j,(xy.Get(cstart+j,i)-network.m_columnmeans[i])/network.m_columnsigmas[i]);
|
|
else
|
|
batch4buf.Set(entryoffs+j,xy.Get(cstart+j,i)-network.m_columnmeans[i]);
|
|
}
|
|
}
|
|
for(neuronidx=0; neuronidx<ntotal; neuronidx++)
|
|
{
|
|
entryoffs=entrysize*neuronidx;
|
|
offs=istart+neuronidx*m_nfieldwidth;
|
|
neurontype=network.m_structinfo[offs+0];
|
|
if(neurontype>0 || neurontype==-5)
|
|
{
|
|
//--- "activation function" neuron, which takes value of neuron SrcNeuronIdx
|
|
//--- and applies activation function to it.
|
|
//--- This neuron has no weights and no tunable parameters.
|
|
srcneuronidx=network.m_structinfo[offs+2];
|
|
srcentryoffs=entrysize*srcneuronidx;
|
|
for(i=0; i<4; i++)
|
|
{
|
|
MLPActivationFunction(batch4buf[srcentryoffs+i],neurontype,f,df,d2f);
|
|
batch4buf.Set(entryoffs+i,f);
|
|
}
|
|
continue;
|
|
}
|
|
if(neurontype==0)
|
|
{
|
|
//--- "adaptive summator" neuron, whose output is a weighted sum of inputs.
|
|
//--- It has weights, but has no activation function.
|
|
nweights=network.m_structinfo[offs+1];
|
|
srcneuronidx=network.m_structinfo[offs+2];
|
|
srcentryoffs=entrysize*srcneuronidx;
|
|
srcweightidx=network.m_structinfo[offs+3];
|
|
v0=0;
|
|
v1=0;
|
|
v2=0;
|
|
v3=0;
|
|
for(j=0; j<nweights; j++)
|
|
{
|
|
v=network.m_weights[srcweightidx];
|
|
srcweightidx++;
|
|
v0+=v*batch4buf[srcentryoffs];
|
|
v1+=v*batch4buf[srcentryoffs+1];
|
|
v2+=v*batch4buf[srcentryoffs+2];
|
|
v3+=v*batch4buf[srcentryoffs+3];
|
|
srcentryoffs+=entrysize;
|
|
}
|
|
batch4buf.Set(entryoffs,v0);
|
|
batch4buf.Set(entryoffs+1,v1);
|
|
batch4buf.Set(entryoffs+2,v2);
|
|
batch4buf.Set(entryoffs+3,v3);
|
|
continue;
|
|
}
|
|
if(neurontype<0)
|
|
{
|
|
bflag=false;
|
|
switch(neurontype)
|
|
{
|
|
case -2:
|
|
//--- Input neuron, left unchanged
|
|
bflag=true;
|
|
break;
|
|
case -3:
|
|
//--- "-1" neuron
|
|
batch4buf.Set(entryoffs,-1);
|
|
batch4buf.Set(entryoffs+1,-1);
|
|
batch4buf.Set(entryoffs+2,-1);
|
|
batch4buf.Set(entryoffs+3,-1);
|
|
bflag=true;
|
|
break;
|
|
case -4:
|
|
//--- "0" neuron
|
|
batch4buf.Set(entryoffs,0);
|
|
batch4buf.Set(entryoffs+1,0);
|
|
batch4buf.Set(entryoffs+2,0);
|
|
batch4buf.Set(entryoffs+3,0);
|
|
bflag=true;
|
|
break;
|
|
}
|
|
if(!CAp::Assert(bflag,__FUNCTION__": internal error - unknown neuron type!"))
|
|
return;
|
|
continue;
|
|
}
|
|
}
|
|
//--- SOFTMAX normalization or scaling.
|
|
if(!CAp::Assert(network.m_structinfo[6]==0 || network.m_structinfo[6]==1,__FUNCTION__": unknown normalization type!"))
|
|
return;
|
|
if(network.m_structinfo[6]==1)
|
|
{
|
|
//--- SOFTMAX-normalized network.
|
|
//--- First, calculate (V0,V1,V2,V3) - component-wise maximum
|
|
//--- of output neurons. This vector of maximum values will be
|
|
//--- used for normalization of outputs prior to calculating
|
|
//--- exponentials.
|
|
//--- NOTE: the only purpose of this stage is to prevent overflow
|
|
//--- during calculation of exponentials. With this stage
|
|
//--- we make sure that all exponentials are calculated
|
|
//--- with non-positive argument. If you load (0,0,0,0) to
|
|
//--- (V0,V1,V2,V3), your program will continue working -
|
|
//--- although with less robustness.
|
|
entryoffs=entrysize*(ntotal-nout);
|
|
v0=batch4buf[entryoffs+0];
|
|
v1=batch4buf[entryoffs+1];
|
|
v2=batch4buf[entryoffs+2];
|
|
v3=batch4buf[entryoffs+3];
|
|
entryoffs+=entrysize;
|
|
for(i=1; i<nout; i++)
|
|
{
|
|
v=batch4buf[entryoffs];
|
|
if(v>v0)
|
|
v0=v;
|
|
v=batch4buf[entryoffs+1];
|
|
if(v>v1)
|
|
v1=v;
|
|
v=batch4buf[entryoffs+2];
|
|
if(v>v2)
|
|
v2=v;
|
|
v=batch4buf[entryoffs+3];
|
|
if(v>v3)
|
|
v3=v;
|
|
entryoffs+=entrysize;
|
|
}
|
|
//--- Then, calculate exponentials and place them to part of the
|
|
//--- array which is located past the last entry. We also
|
|
//--- calculate sum of exponentials.
|
|
entryoffs=entrysize*(ntotal-nout);
|
|
offs0=entrysize*ntotal;
|
|
s0=0;
|
|
s1=0;
|
|
s2=0;
|
|
s3=0;
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
v=MathExp(batch4buf[entryoffs]-v0);
|
|
s0+=v;
|
|
batch4buf.Set(offs0,v);
|
|
v=MathExp(batch4buf[entryoffs+1]-v1);
|
|
s1+=v;
|
|
batch4buf.Set(offs0+1,v);
|
|
v=MathExp(batch4buf[entryoffs+2]-v2);
|
|
s2+=v;
|
|
batch4buf.Set(offs0+2,v);
|
|
v=MathExp(batch4buf[entryoffs+3]-v3);
|
|
s3+=v;
|
|
batch4buf.Set(offs0+3,v);
|
|
entryoffs+=entrysize;
|
|
offs0+=chunksize;
|
|
}
|
|
//--- Write SOFTMAX-normalized values to the output array.
|
|
offs0=entrysize*ntotal;
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
if(csize>0)
|
|
xy.Set(cstart,nin+i,batch4buf[offs0]/s0);
|
|
if(csize>1)
|
|
xy.Set(cstart+1,nin+i,batch4buf[offs0+1]/s1);
|
|
if(csize>2)
|
|
xy.Set(cstart+2,nin+i,batch4buf[offs0+2]/s2);
|
|
if(csize>3)
|
|
xy.Set(cstart+3,nin+i,batch4buf[offs0+3]/s3);
|
|
offs0+=chunksize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Regression network with sum-of-squares function.
|
|
//--- For each NOut of last neurons:
|
|
//--- * calculate difference between actual and desired output
|
|
//--- * calculate dError/dOut for this neuron (proportional to difference)
|
|
//--- * store in in last 4 components of entry (these values are used
|
|
//--- to start backpropagation)
|
|
//--- * update error
|
|
for(i=0; i<nout; i++)
|
|
{
|
|
v0=network.m_columnsigmas[nin+i];
|
|
v1=network.m_columnmeans[nin+i];
|
|
entryoffs=entrysize*(ntotal-nout+i);
|
|
for(j=0; j<csize; j++)
|
|
xy.Set(cstart+j,nin+i,batch4buf[entryoffs+j]*v0+v1);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns T*Ln(T/Z), guarded against overflow/underflow. |
|
|
//| Internal subroutine. |
|
|
//+------------------------------------------------------------------+
|
|
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);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs backward pass of neural network |
|
|
//| randimization: |
|
|
//| * it assumes that Network.Weights stores standard deviation of |
|
|
//| weights (weights are not generated yet, only their deviations|
|
|
//| are present) |
|
|
//| * it sets deviations of weights which feed NeuronIdx-th neuron |
|
|
//| to specified value |
|
|
//| * it recursively passes to deeper neuron and modifies their |
|
|
//| weights |
|
|
//| * it stops after encountering nonlinear neurons, linear |
|
|
//| activation function, input neurons, "0" and "-1" neurons |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPBase::RandomizeBackwardPass(CMultilayerPerceptron &network,
|
|
int neuronidx,double v)
|
|
{
|
|
//--- create variables
|
|
int istart=0;
|
|
int neurontype=0;
|
|
int n1=0;
|
|
int n2=0;
|
|
int w1=0;
|
|
int w2=0;
|
|
int offs=0;
|
|
|
|
istart=network.m_structinfo[5];
|
|
neurontype=network.m_structinfo[istart+neuronidx*m_nfieldwidth];
|
|
switch(neurontype)
|
|
{
|
|
case -2:
|
|
//--- Input neuron - stop
|
|
break;
|
|
case -3:
|
|
//--- "-1" neuron: stop
|
|
break;
|
|
case -4:
|
|
//--- "0" neuron: stop
|
|
break;
|
|
case 0:
|
|
//--- Adaptive summator neuron:
|
|
//--- * modify deviations of its weights
|
|
//--- * recursively call this function for its inputs
|
|
offs=istart+neuronidx*m_nfieldwidth;
|
|
n1=network.m_structinfo[offs+2];
|
|
n2=n1+network.m_structinfo[offs+1];
|
|
w1=network.m_structinfo[offs+3];
|
|
w2=w1+network.m_structinfo[offs+1];
|
|
for(int i=w1; i<w2; i++)
|
|
network.m_weights.Set(i,v);
|
|
for(int i=n1; i<n2; i++)
|
|
RandomizeBackwardPass(network,i,v);
|
|
break;
|
|
case -5:
|
|
//--- Linear activation function: stop
|
|
break;
|
|
default:
|
|
if(!CAp::Assert(neurontype>0,__FUNCTION__": unexpected neuron type"))
|
|
return;
|
|
break;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary class for CLogit |
|
|
//+------------------------------------------------------------------+
|
|
class CLogitModel
|
|
{
|
|
public:
|
|
CRowDouble m_w;
|
|
//--- constructor, destructor
|
|
CLogitModel(void) {}
|
|
~CLogitModel(void) {}
|
|
//--- copy
|
|
void Copy(const CLogitModel &obj);
|
|
//--- overloading
|
|
void operator=(const CLogitModel &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CLogitModel::Copy(const CLogitModel &obj)
|
|
{
|
|
//--- copy array
|
|
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) { m_innerobj.Copy(obj); }
|
|
~CLogitModelShell(void) {}
|
|
//--- method
|
|
CLogitModel *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary class for CLogit |
|
|
//+------------------------------------------------------------------+
|
|
struct CLogitMCState
|
|
{
|
|
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) { ZeroMemory(this); }
|
|
~CLogitMCState(void) {}
|
|
//---
|
|
void Copy(const CLogitMCState &obj);
|
|
//--- overloading
|
|
void operator=(const CLogitMCState &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CLogitMCState::Copy(const CLogitMCState &obj)
|
|
{
|
|
m_brackt=obj.m_brackt;
|
|
m_stage1=obj.m_stage1;
|
|
m_infoc=obj.m_infoc;
|
|
m_dg=obj.m_dg;
|
|
m_dgm=obj.m_dgm;
|
|
m_dginit=obj.m_dginit;
|
|
m_dgtest=obj.m_dgtest;
|
|
m_dgx=obj.m_dgx;
|
|
m_dgxm=obj.m_dgxm;
|
|
m_dgy=obj.m_dgy;
|
|
m_dgym=obj.m_dgym;
|
|
m_finit=obj.m_finit;
|
|
m_ftest1=obj.m_ftest1;
|
|
m_fm=obj.m_fm;
|
|
m_fx=obj.m_fx;
|
|
m_fxm=obj.m_fxm;
|
|
m_fy=obj.m_fy;
|
|
m_fym=obj.m_fym;
|
|
m_stx=obj.m_stx;
|
|
m_sty=obj.m_sty;
|
|
m_stmin=obj.m_stmin;
|
|
m_stmax=obj.m_stmax;
|
|
m_width=obj.m_width;
|
|
m_width1=obj.m_width1;
|
|
m_xtrapf=obj.m_xtrapf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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) { ZeroMemory(this); }
|
|
~CMNLReport(void) {}
|
|
//--- copy
|
|
void Copy(CMNLReport &obj);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| 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) { m_innerobj.Copy(obj); }
|
|
~CMNLReportShell(void) {}
|
|
//--- methods
|
|
int GetNGrad(void);
|
|
void SetNGrad(const int i);
|
|
int GetNHess(void);
|
|
void SetNHess(const int i);
|
|
CMNLReport *GetInnerObj(void);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable ngrad |
|
|
//+------------------------------------------------------------------+
|
|
int CMNLReportShell::GetNGrad(void)
|
|
{
|
|
return(m_innerobj.m_ngrad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable ngrad |
|
|
//+------------------------------------------------------------------+
|
|
void CMNLReportShell::SetNGrad(const int i)
|
|
{
|
|
m_innerobj.m_ngrad=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable nhess |
|
|
//+------------------------------------------------------------------+
|
|
int CMNLReportShell::GetNHess(void)
|
|
{
|
|
return(m_innerobj.m_nhess);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable nhess |
|
|
//+------------------------------------------------------------------+
|
|
void CMNLReportShell::SetNHess(const int i)
|
|
{
|
|
m_innerobj.m_nhess=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMNLReport *CMNLReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Class logit model |
|
|
//+------------------------------------------------------------------+
|
|
class CLogit
|
|
{
|
|
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;
|
|
//--- 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 MNLProcess(CLogitModel &lm,CRowDouble &x,CRowDouble &y);
|
|
static void MNLProcessI(CLogitModel &lm,double &x[],double &y[]);
|
|
static void MNLProcessI(CLogitModel &lm,CRowDouble &x,CRowDouble &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);
|
|
|
|
private:
|
|
static void MNLIExp(CRowDouble &w,CRowDouble &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,CRowDouble &x,double &f,CRowDouble &g,CRowDouble &s,double &stp,int &info,int &nfev,CRowDouble &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);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| 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;
|
|
//+------------------------------------------------------------------+
|
|
//| 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 |
|
|
//+------------------------------------------------------------------+
|
|
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
|
|
CRowDouble g;
|
|
vector<double> x;
|
|
vector<double> y;
|
|
vector<double> wbase;
|
|
CRowDouble wdir;
|
|
CRowDouble 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; i++)
|
|
{
|
|
//--- check
|
|
if((int)MathRound(xy.Get(i,nvars))<0 || (int)MathRound(xy.Get(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
|
|
lm.m_w.Resize(ssize);
|
|
//--- change values
|
|
lm.m_w.Set(0,ssize);
|
|
lm.m_w.Set(1,m_logitvnum);
|
|
lm.m_w.Set(2,nvars);
|
|
lm.m_w.Set(3,nclasses);
|
|
lm.m_w.Set(4,offs);
|
|
//--- Degenerate case: all outputs are equal
|
|
allsame=true;
|
|
for(i=1; i<npoints; i++)
|
|
{
|
|
//--- check
|
|
if((int)MathRound(xy.Get(i,nvars))!=(int)MathRound(xy.Get(i-1,nvars)))
|
|
allsame=false;
|
|
}
|
|
//--- check
|
|
if(allsame)
|
|
{
|
|
for(i=0; i<=(nvars+1)*(nclasses-1)-1; i++)
|
|
lm.m_w.Set(offs+i,0);
|
|
//--- change values
|
|
v=-(2*MathLog(CMath::m_minrealnumber));
|
|
k=(int)MathRound(xy.Get(0,nvars));
|
|
//--- check
|
|
if(k==nclasses-1)
|
|
{
|
|
for(i=0; i<nclasses-1; i++)
|
|
lm.m_w.Set(offs+i*(nvars+1)+nvars,-v);
|
|
}
|
|
else
|
|
{
|
|
for(i=0; i<nclasses-1; i++)
|
|
//--- check
|
|
if(i==k)
|
|
lm.m_w.Set(offs+i*(nvars+1)+nvars,v);
|
|
else
|
|
lm.m_w.Set(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.Set(i,(2*CMath::RandomReal()-1)/nvars);
|
|
//--- allocation
|
|
g.Resize(wcount);
|
|
h.Resize(wcount,wcount);
|
|
wbase.Resize(wcount);
|
|
wdir.Resize(wcount);
|
|
work.Resize(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=network.m_weights.Dot(network.m_weights);
|
|
//--- change value
|
|
e=e+0.5*decay*v;
|
|
g+=network.m_weights*decay+0;
|
|
rep.m_ngrad++;
|
|
//--- Setup optimization scheme
|
|
wdir=g*(-1.0)+0;
|
|
v=wdir.Dot(wdir);
|
|
//--- change values
|
|
wstep=MathSqrt(v);
|
|
wdir/=MathSqrt(v);
|
|
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=network.m_weights.Dot(network.m_weights);
|
|
//--- change value
|
|
e=e+0.5*decay*v;
|
|
g+=network.m_weights*decay+0;
|
|
rep.m_ngrad++;
|
|
//--- 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=network.m_weights.Dot(network.m_weights);
|
|
//--- change value
|
|
e=e+0.5*decay*v;
|
|
g+=network.m_weights*decay+0;
|
|
h.Diag(h.Diag(0)+decay,0);
|
|
rep.m_nhess++;
|
|
//--- 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.
|
|
wdir*=(-1.0);
|
|
spd=true;
|
|
}
|
|
else
|
|
{
|
|
//--- H is indefinite.
|
|
//--- Step in gradient direction.
|
|
wdir=g;
|
|
wdir*=(-1.0);
|
|
spd=false;
|
|
}
|
|
//--- Optimize in WDir direction
|
|
v=wdir.Dot(wdir);
|
|
//--- change values
|
|
wstep=MathSqrt(v);
|
|
wdir/=MathSqrt(v);
|
|
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=network.m_weights.Dot(network.m_weights);
|
|
//--- change value
|
|
e=e+0.5*decay*v;
|
|
g+=network.m_weights*decay+0;
|
|
rep.m_ngrad++;
|
|
//--- 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; i_++)
|
|
lm.m_w.Set(i_,network.m_weights[i_+i1_]);
|
|
for(k=0; k<nvars; k++)
|
|
{
|
|
for(i=0; i<nclasses-1; 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.Set(j+k,v/s);
|
|
lm.m_w.Add(j+nvars,v*network.m_columnmeans[k]/s);
|
|
}
|
|
}
|
|
//--- calculation
|
|
for(k=0; k<nclasses-1; k++)
|
|
lm.m_w.Mul(offs+(nvars+1)*k+nvars,-1.0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
void CLogit::MNLProcess(CLogitModel &lm,double &x[],double &y[])
|
|
{
|
|
CRowDouble X=x;
|
|
CRowDouble Y;
|
|
MNLProcess(lm,X,Y);
|
|
Y.ToArray(y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CLogit::MNLProcess(CLogitModel &lm,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int nvars=(int)MathRound(lm.m_w[2]);
|
|
int nclasses=(int)MathRound(lm.m_w[3]);
|
|
int offs=(int)MathRound(lm.m_w[4]);
|
|
int i1=0;
|
|
double s=0;
|
|
//--- check
|
|
if(!CAp::Assert(lm.m_w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
|
|
return;
|
|
//--- function call
|
|
MNLIExp(lm.m_w,x);
|
|
s=0;
|
|
//--- calculation
|
|
i1=offs+(nvars+1)*(nclasses-1);
|
|
for(int i=i1; i<(i1+nclasses); i++)
|
|
s+=lm.m_w[i];
|
|
//--- check
|
|
if(CAp::Len(y)<nclasses)
|
|
y.Resize(nclasses);
|
|
//--- change values
|
|
for(int i=0; i<=nclasses-1; i++)
|
|
y.Set(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. |
|
|
//+------------------------------------------------------------------+
|
|
void CLogit::MNLProcessI(CLogitModel &lm,double &x[],double &y[])
|
|
{
|
|
MNLProcess(lm,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CLogit::MNLProcessI(CLogitModel &lm,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
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 |
|
|
//+------------------------------------------------------------------+
|
|
void CLogit::MNLUnpack(CLogitModel &lm,CMatrixDouble &a,int &nvars,
|
|
int &nclasses)
|
|
{
|
|
//--- create variables
|
|
int offs=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(int i=0; i<=nclasses-2; i++)
|
|
{
|
|
i1_=offs+i*(nvars+1);
|
|
for(int i_=0; i_<=nvars; i_++)
|
|
a.Set(i,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. |
|
|
//+------------------------------------------------------------------+
|
|
void CLogit::MNLPack(CMatrixDouble &a,const int nvars,const int nclasses,
|
|
CLogitModel &lm)
|
|
{
|
|
//--- create variables
|
|
int offs=5;
|
|
int wdim=(nvars+1)*(nclasses-1);
|
|
int ssize=5+(nvars+1)*(nclasses-1)+nclasses;
|
|
int i1_=0;
|
|
//--- allocation
|
|
lm.m_w.Resize(ssize);
|
|
//--- initialization
|
|
lm.m_w.Set(0,ssize);
|
|
lm.m_w.Set(1,m_logitvnum);
|
|
lm.m_w.Set(2,nvars);
|
|
lm.m_w.Set(3,nclasses);
|
|
lm.m_w.Set(4,offs);
|
|
//--- calculation
|
|
for(int i=0; i<nclasses-1; i++)
|
|
{
|
|
i1_=-(offs+i*(nvars+1));
|
|
for(int i_=offs+i*(nvars+1); i_<=offs+i*(nvars+1)+nvars; i_++)
|
|
lm.m_w.Set(i_,a.Get(i,i_+i1_));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copying of LogitModel strucure |
|
|
//| INPUT PARAMETERS: |
|
|
//| LM1 - original |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| LM2 - copy |
|
|
//+------------------------------------------------------------------+
|
|
void CLogit::MNLCopy(CLogitModel &lm1,CLogitModel &lm2)
|
|
{
|
|
//--- initialization
|
|
int k=(int)MathRound(lm1.m_w[0]);
|
|
//--- allocation
|
|
lm2.m_w.Resize(k);
|
|
//--- copy
|
|
lm2.m_w=lm1.m_w;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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)). |
|
|
//+------------------------------------------------------------------+
|
|
double CLogit::MNLAvgCE(CLogitModel &lm,CMatrixDouble &xy,const int npoints)
|
|
{
|
|
//--- create variables
|
|
double result=0;
|
|
int nvars=(int)MathRound(lm.m_w[2]);
|
|
int nclasses=(int)MathRound(lm.m_w[3]);
|
|
//--- creating arrays
|
|
CRowDouble workx;
|
|
CRowDouble worky;
|
|
//--- check
|
|
if(!CAp::Assert(lm.m_w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
|
|
return(EMPTY_VALUE);
|
|
//--- allocation
|
|
worky.Resize(nclasses);
|
|
//--- calculation
|
|
for(int i=0; i<npoints; 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
|
|
workx=xy[i]+0;
|
|
//--- function call
|
|
MNLProcess(lm,workx,worky);
|
|
//--- check
|
|
if(worky[(int)MathRound(xy.Get(i,nvars))]>0.0)
|
|
result-=MathLog(worky[(int)MathRound(xy.Get(i,nvars))]);
|
|
else
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
double CLogit::MNLRelClsError(CLogitModel &lm,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
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). |
|
|
//+------------------------------------------------------------------+
|
|
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). |
|
|
//+------------------------------------------------------------------+
|
|
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). |
|
|
//+------------------------------------------------------------------+
|
|
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 |
|
|
//+------------------------------------------------------------------+
|
|
int CLogit::MNLClsError(CLogitModel &lm,CMatrixDouble &xy,const int npoints)
|
|
{
|
|
//--- create variables
|
|
int result=0;
|
|
int nvars=(int)MathRound(lm.m_w[2]);
|
|
int nclasses=(int)MathRound(lm.m_w[3]);
|
|
int nmax=0;
|
|
//--- creating arrays
|
|
CRowDouble workx;
|
|
CRowDouble worky;
|
|
//--- check
|
|
if(!CAp::Assert(lm.m_w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
|
|
return(-1);
|
|
//--- allocation
|
|
worky.Resize(nclasses);
|
|
//--- calculation
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
//--- Process
|
|
workx=xy[i]+0;
|
|
//--- function call
|
|
MNLProcess(lm,workx,worky);
|
|
//--- Logit version of the answer
|
|
nmax=0;
|
|
for(int j=0; j<nclasses; j++)
|
|
{
|
|
//--- check
|
|
if(worky[j]>worky[nmax])
|
|
nmax=j;
|
|
}
|
|
//--- compare
|
|
if(nmax!=(int)MathRound(xy[i][nvars]))
|
|
result++;
|
|
}
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal subroutine. Places exponents of the anti-overflow |
|
|
//| shifted internal linear outputs into the service part of the W |
|
|
//| array. |
|
|
//+------------------------------------------------------------------+
|
|
void CLogit::MNLIExp(CRowDouble &w,CRowDouble &x)
|
|
{
|
|
//--- create variables
|
|
int nvars=(int)MathRound(w[2]);
|
|
int nclasses=(int)MathRound(w[3]);
|
|
int offs=(int)MathRound(w[4]);
|
|
int i1=0;
|
|
double v=0;
|
|
double mx=0;
|
|
int i1_=0;
|
|
//--- check
|
|
if(!CAp::Assert(w[1]==m_logitvnum,__FUNCTION__+": unexpected model version"))
|
|
return;
|
|
//--- calculation
|
|
i1=offs+(nvars+1)*(nclasses-1);
|
|
for(int i=0; i<nclasses-1; i++)
|
|
{
|
|
//--- change values
|
|
i1_=-(offs+i*(nvars+1));
|
|
v=0.0;
|
|
for(int i_=offs+i*(nvars+1); i_<=offs+i*(nvars+1)+nvars-1; i_++)
|
|
v+=w[i_]*x[i_+i1_];
|
|
w.Set(i1+i,v+w[offs+i*(nvars+1)+nvars]);
|
|
}
|
|
//--- change values
|
|
w.Set(i1+nclasses-1,0);
|
|
mx=0;
|
|
//--- calculation
|
|
for(int i=i1; i<i1+nclasses; i++)
|
|
mx=MathMax(mx,w[i]);
|
|
for(int i=i1; i<i1+nclasses; i++)
|
|
w.Set(i,MathExp(w[i]-mx));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Calculation of all types of errors |
|
|
//+------------------------------------------------------------------+
|
|
void CLogit::MNLAllErrors(CLogitModel &lm,CMatrixDouble &xy,
|
|
const int npoints,double &relcls,
|
|
double &avgce,double &rms,double &avg,
|
|
double &avgrel)
|
|
{
|
|
//--- create variables
|
|
int nvars=(int)MathRound(lm.m_w[2]);
|
|
int nclasses=(int)MathRound(lm.m_w[3]);
|
|
//--- creating arrays
|
|
CRowDouble buf;
|
|
CRowDouble workx;
|
|
CRowDouble y;
|
|
CRowDouble 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;
|
|
//--- allocation
|
|
workx.Resize(nvars);
|
|
y.Resize(nclasses);
|
|
dy.Resize(1);
|
|
//--- function call
|
|
CBdSS::DSErrAllocate(nclasses,buf);
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
workx=xy[i]+0;
|
|
//--- function call
|
|
MNLProcess(lm,workx,y);
|
|
//--- change value
|
|
dy.Set(0,xy.Get(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 |
|
|
//+------------------------------------------------------------------+
|
|
void CLogit::MNLMCSrch(const int n,CRowDouble &x,double &f,CRowDouble &g,
|
|
CRowDouble &s,double &stp,int &info,int &nfev,
|
|
CRowDouble &wa,CLogitMCState &state,int &stage)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
double p5=0;
|
|
double p66=0;
|
|
double zero=0;
|
|
//--- init
|
|
p5=0.5;
|
|
p66=0.66;
|
|
state.m_xtrapf=4.0;
|
|
zero=0;
|
|
//--- Main cycle
|
|
while(true)
|
|
{
|
|
//--- check
|
|
switch(stage)
|
|
{
|
|
case 0:
|
|
//--- NEXT
|
|
stage=2;
|
|
break;
|
|
//--- check
|
|
case 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=g.Dot(s);
|
|
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;
|
|
wa=x;
|
|
//--- 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;
|
|
break;
|
|
case 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.
|
|
x=wa+s*stp+0;
|
|
//--- next
|
|
stage=4;
|
|
return;
|
|
case 4:
|
|
info=0;
|
|
nfev++;
|
|
//--- calculation
|
|
v=g.Dot(s);
|
|
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;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Auxiliary function for MNLMCSrch |
|
|
//+------------------------------------------------------------------+
|
|
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
|
|
CRowInt m_states;
|
|
CRowInt m_ct;
|
|
CRowDouble m_pw;
|
|
CRowDouble m_tmpp;
|
|
CRowDouble m_effectivew;
|
|
CRowDouble m_effectivebndl;
|
|
CRowDouble m_effectivebndu;
|
|
CRowInt m_effectivect;
|
|
CRowDouble 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(const CMCPDState &obj);
|
|
//--- overloading
|
|
void operator=(const CMCPDState &obj) { Copy(obj); }
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMCPDState::CMCPDState(void)
|
|
{
|
|
//--- copy variables
|
|
m_n=0;
|
|
m_npairs=0;
|
|
m_ccnt=0;
|
|
m_regterm=0;
|
|
m_repinneriterationscount=0;
|
|
m_repouteriterationscount=0;
|
|
m_repnfev=0;
|
|
m_repterminationtype=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMCPDState::Copy(const 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
|
|
m_states=obj.m_states;
|
|
m_ct=obj.m_ct;
|
|
m_pw=obj.m_pw;
|
|
m_tmpp=obj.m_tmpp;
|
|
m_effectivew=obj.m_effectivew;
|
|
m_effectivebndl=obj.m_effectivebndl;
|
|
m_effectivebndu=obj.m_effectivebndu;
|
|
m_effectivect=obj.m_effectivect;
|
|
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) { m_innerobj.Copy(obj); }
|
|
~CMCPDStateShell(void) {}
|
|
//--- method
|
|
CMCPDState *GetInnerObj(void) { 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) { ZeroMemory(this); }
|
|
~CMCPDReport(void) {}
|
|
//--- copy
|
|
void Copy(const CMCPDReport &obj);
|
|
//--- overloading
|
|
void operator=(const CMCPDReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMCPDReport::Copy(const 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) { m_innerobj.Copy(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);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable inneriterationscount |
|
|
//+------------------------------------------------------------------+
|
|
int CMCPDReportShell::GetInnerIterationsCount(void)
|
|
{
|
|
return(m_innerobj.m_inneriterationscount);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable inneriterationscount |
|
|
//+------------------------------------------------------------------+
|
|
void CMCPDReportShell::SetInnerIterationsCount(const int i)
|
|
{
|
|
m_innerobj.m_inneriterationscount=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable outeriterationscount |
|
|
//+------------------------------------------------------------------+
|
|
int CMCPDReportShell::GetOuterIterationsCount(void)
|
|
{
|
|
return(m_innerobj.m_outeriterationscount);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable outeriterationscount |
|
|
//+------------------------------------------------------------------+
|
|
void CMCPDReportShell::SetOuterIterationsCount(const int i)
|
|
{
|
|
m_innerobj.m_outeriterationscount=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable nfev |
|
|
//+------------------------------------------------------------------+
|
|
int CMCPDReportShell::GetNFev(void)
|
|
{
|
|
return(m_innerobj.m_nfev);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable nfev |
|
|
//+------------------------------------------------------------------+
|
|
void CMCPDReportShell::SetNFev(const int i)
|
|
{
|
|
m_innerobj.m_nfev=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
int CMCPDReportShell::GetTerminationType(void)
|
|
{
|
|
return(m_innerobj.m_terminationtype);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable terminationtype |
|
|
//+------------------------------------------------------------------+
|
|
void CMCPDReportShell::SetTerminationType(const int i)
|
|
{
|
|
m_innerobj.m_terminationtype=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMCPDReport *CMCPDReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Markov chains class |
|
|
//+------------------------------------------------------------------+
|
|
class CMarkovCPD
|
|
{
|
|
public:
|
|
//--- constant
|
|
static const double m_xtol;
|
|
//--- 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 MCPDSetLC(CMCPDState &s,CMatrixDouble &c,CRowInt &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 MCPDSetPredictionWeights(CMCPDState &s,CRowDouble &pw);
|
|
static void MCPDSolve(CMCPDState &s);
|
|
static void MCPDResults(CMCPDState &s,CMatrixDouble &p,CMCPDReport &rep);
|
|
|
|
private:
|
|
static void MCPDInit(const int n,const int entrystate,const int exitstate,CMCPDState &s);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize constant |
|
|
//+------------------------------------------------------------------+
|
|
const double CMarkovCPD::m_xtol=1.0E-8;
|
|
//+------------------------------------------------------------------+
|
|
//| 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 |
|
|
//+------------------------------------------------------------------+
|
|
void CMarkovCPD::MCPDCreate(const int n,CMCPDState &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": 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 |
|
|
//+------------------------------------------------------------------+
|
|
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 |
|
|
//+------------------------------------------------------------------+
|
|
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 |
|
|
//+------------------------------------------------------------------+
|
|
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) |
|
|
//+------------------------------------------------------------------+
|
|
void CMarkovCPD::MCPDAddTrack(CMCPDState &s,CMatrixDouble &xy,
|
|
const int k)
|
|
{
|
|
//--- create variables
|
|
int n=s.m_n;
|
|
double s0=0;
|
|
double s1=0;
|
|
//--- check
|
|
if(!CAp::Assert(k>=0,__FUNCTION__+": K<0"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(xy)>=n,__FUNCTION__+": Cols(XY)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)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(int i=0; i<k; i++)
|
|
{
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(xy.Get(i,j)>=0.0,__FUNCTION__+": XY contains negative elements"))
|
|
return;
|
|
}
|
|
}
|
|
//--- check
|
|
if(k<2)
|
|
return;
|
|
//--- check
|
|
if((int)CAp::Rows(s.m_data)<s.m_npairs+k-1)
|
|
CApServ::RMatrixResize(s.m_data,MathMax(2*(int)CAp::Rows(s.m_data),s.m_npairs+k-1),2*n);
|
|
//--- calculation
|
|
for(int i=0; i<k-1; i++)
|
|
{
|
|
s0=0;
|
|
s1=0;
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
//--- check
|
|
if(s.m_states[j]>=0)
|
|
s0+=xy.Get(i,j);
|
|
//--- check
|
|
if(s.m_states[j]<=0)
|
|
s1+=xy.Get(i+1,j);
|
|
}
|
|
//--- check
|
|
if(s0>0.0 && s1>0.0)
|
|
{
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
//--- check
|
|
if(s.m_states[j]>=0)
|
|
s.m_data.Set(s.m_npairs,j,xy.Get(i,j)/s0);
|
|
else
|
|
s.m_data.Set(s.m_npairs,j,0.0);
|
|
//--- check
|
|
if(s.m_states[j]<=0)
|
|
s.m_data.Set(s.m_npairs,n+j,xy.Get(i+1,j)/s1);
|
|
else
|
|
s.m_data.Set(s.m_npairs,n+j,0.0);
|
|
}
|
|
//--- change value
|
|
s.m_npairs++;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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(). |
|
|
//+------------------------------------------------------------------+
|
|
void CMarkovCPD::MCPDSetEC(CMCPDState &s,CMatrixDouble &ec)
|
|
{
|
|
int n=s.m_n;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(ec)>=n,__FUNCTION__+": Cols(EC)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(ec)>=n,__FUNCTION__+": Rows(EC)<N"))
|
|
return;
|
|
//--- calculation
|
|
if(!CAp::Assert(CApServ::IsFiniteOrNaNMatrix(ec,n,n),"MCPDSetEC: EC containts infinite elements"))
|
|
return;
|
|
s.m_ec=ec;
|
|
s.m_ec.Resize(n,n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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(). |
|
|
//+------------------------------------------------------------------+
|
|
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) || MathIsValidNumber(c),"MCPDAddEC: C is not finite number or NAN"))
|
|
return;
|
|
s.m_ec.Set(i,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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMarkovCPD::MCPDSetBC(CMCPDState &s,CMatrixDouble &bndl,
|
|
CMatrixDouble &bndu)
|
|
{
|
|
int n=s.m_n;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(bndl)>=n,__FUNCTION__+": Cols(BndL)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(bndl)>=n,__FUNCTION__+": Rows(BndL)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(bndu)>=n,__FUNCTION__+": Cols(BndU)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(bndu)>=n,__FUNCTION__+": Rows(BndU)<N"))
|
|
return;
|
|
//--- calculation
|
|
for(int i=0; i<n; i++)
|
|
{
|
|
for(int j=0; j<n; j++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(bndl.Get(i,j)) || CInfOrNaN::IsNegativeInfinity(bndl.Get(i,j)),"MCPDSetBC: BndL containts NAN or +INF"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(bndu.Get(i,j)) || CInfOrNaN::IsPositiveInfinity(bndu.Get(i,j)),"MCPDSetBC: BndU containts NAN or -INF"))
|
|
return;
|
|
//--- change values
|
|
s.m_bndl.Set(i,j,bndl.Get(i,j));
|
|
s.m_bndu.Set(i,j,bndu.Get(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 |
|
|
//+------------------------------------------------------------------+
|
|
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(MathIsValidNumber(bndl) || CInfOrNaN::IsNegativeInfinity(bndl),"MCPDAddBC: BndL is NAN or +INF"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(bndu) || CInfOrNaN::IsPositiveInfinity(bndu),"MCPDAddBC: BndU is NAN or -INF"))
|
|
return;
|
|
//--- change values
|
|
s.m_bndl.Set(i,j,bndl);
|
|
s.m_bndu.Set(i,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 |
|
|
//+------------------------------------------------------------------+
|
|
void CMarkovCPD::MCPDSetLC(CMCPDState &s,CMatrixDouble &c,int &ct[],
|
|
const int k)
|
|
{
|
|
CRowInt Ct=ct;
|
|
MCPDSetLC(s,c,Ct,k);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMarkovCPD::MCPDSetLC(CMCPDState &s,CMatrixDouble &c,CRowInt &ct,
|
|
const int k)
|
|
{
|
|
int n=s.m_n;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(c)>=n*n+1,__FUNCTION__+": Cols(C)<N*N+1"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)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;
|
|
//--- copy
|
|
s.m_c=c;
|
|
s.m_ct=ct;
|
|
s.m_c.Resize(k,n*n+1);
|
|
s.m_ct.Resize(k);
|
|
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.|
|
|
//+------------------------------------------------------------------+
|
|
void CMarkovCPD::MCPDSetTikhonovRegularizer(CMCPDState &s,const double v)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMarkovCPD::MCPDSetPrior(CMCPDState &s,CMatrixDouble &pp)
|
|
{
|
|
int n=s.m_n;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Cols(pp)>=n,__FUNCTION__+": Cols(PP)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert((int)CAp::Rows(pp)>=n,__FUNCTION__+": Rows(PP)<K"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(pp,n,n),__FUNCTION__+": PP containts infinite elements"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(pp.Min()>=0.0 && pp.Max()<=1.0,__FUNCTION__+": PP.Get(i,j) is less than 0.0 or greater than 1.0"))
|
|
return;
|
|
//--- change value
|
|
s.m_priorp=pp;
|
|
s.m_priorp.Resize(n,n);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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 |
|
|
//+------------------------------------------------------------------+
|
|
void CMarkovCPD::MCPDSetPredictionWeights(CMCPDState &s,double &pw[])
|
|
{
|
|
CRowDouble Pw=pw;
|
|
MCPDSetPredictionWeights(s,Pw);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMarkovCPD::MCPDSetPredictionWeights(CMCPDState &s,CRowDouble &pw)
|
|
{
|
|
int n=s.m_n;
|
|
//--- check
|
|
if(!CAp::Assert(CAp::Len(pw)>=n,__FUNCTION__+": Length(PW)<N"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(pw,n),__FUNCTION__+": PW containts infinite or NAN elements"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(pw.Min()>=0.0,__FUNCTION__+": PW containts negative elements"))
|
|
return;
|
|
s.m_pw=pw;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMarkovCPD::MCPDSolve(CMCPDState &s)
|
|
{
|
|
//--- create variables
|
|
int n=s.m_n;
|
|
int npairs=s.m_npairs;
|
|
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;
|
|
//--- init fields of S
|
|
s.m_repterminationtype=0;
|
|
s.m_repinneriterationscount=0;
|
|
s.m_repouteriterationscount=0;
|
|
s.m_repnfev=0;
|
|
s.m_p.Fill(AL_NaN);
|
|
//--- Generate "effective" weights for prediction and calculate preconditioner
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(s.m_pw[i]==0.0)
|
|
{
|
|
//--- change values
|
|
v=0;
|
|
k=0;
|
|
for(j=0; j<npairs; j++)
|
|
//--- check
|
|
if(s.m_data.Get(j,n+i)!=0.0)
|
|
{
|
|
v+=s.m_data.Get(j,n+i);
|
|
k++;
|
|
}
|
|
//--- check
|
|
if(k!=0)
|
|
s.m_effectivew.Set(i,k/v);
|
|
else
|
|
s.m_effectivew.Set(i,1.0);
|
|
}
|
|
else
|
|
s.m_effectivew.Set(i,s.m_pw[i]);
|
|
}
|
|
//--- calculation
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<n; j++)
|
|
s.m_h.Set(i*n+j,2*s.m_regterm);
|
|
}
|
|
//--- calculation
|
|
for(k=0; k<npairs; k++)
|
|
{
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<n; j++)
|
|
s.m_h.Add(i*n+j,2*CMath::Sqr(s.m_effectivew[i])*CMath::Sqr(s.m_data.Get(k,j)));
|
|
}
|
|
}
|
|
//--- calculation
|
|
for(i=0; i<n; i++)
|
|
for(j=0; j<n; j++)
|
|
//--- check
|
|
if(s.m_h[i*n+j]==0.0)
|
|
s.m_h.Set(i*n+j,1);
|
|
//--- Generate "effective" BndL/BndU
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<n; j++)
|
|
{
|
|
//--- Set default boundary constraints.
|
|
//--- Lower bound is always zero,upper bound is calculated
|
|
//--- with respect to entry/exit states.
|
|
s.m_effectivebndl.Set(i*n+j,0.0);
|
|
//--- check
|
|
if(s.m_states[i]>0 || s.m_states[j]<0)
|
|
s.m_effectivebndu.Set(i*n+j,0.0);
|
|
else
|
|
s.m_effectivebndu.Set(i*n+j,1.0);
|
|
//--- Calculate intersection of the default and user-specified bound constraints.
|
|
//--- This code checks consistency of such combination.
|
|
if(MathIsValidNumber(s.m_bndl.Get(i,j)) && s.m_bndl.Get(i,j)>s.m_effectivebndl[i*n+j])
|
|
s.m_effectivebndl.Set(i*n+j,s.m_bndl.Get(i,j));
|
|
//--- check
|
|
if(MathIsValidNumber(s.m_bndu.Get(i,j)) && s.m_bndu.Get(i,j)<s.m_effectivebndu[i*n+j])
|
|
s.m_effectivebndu.Set(i*n+j,s.m_bndu.Get(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(MathIsValidNumber(s.m_ec.Get(i,j)))
|
|
{
|
|
//--- check
|
|
if(s.m_ec.Get(i,j)<s.m_effectivebndl[i*n+j] || s.m_ec.Get(i,j)>s.m_effectivebndu[i*n+j])
|
|
{
|
|
s.m_repterminationtype=-3;
|
|
return;
|
|
}
|
|
//--- change values
|
|
s.m_effectivebndl.Set(i*n+j,s.m_ec.Get(i,j));
|
|
s.m_effectivebndu.Set(i*n+j,s.m_ec.Get(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; i++)
|
|
{
|
|
for(j=0; j<=n*n; j++)
|
|
s.m_effectivec.Set(i,j,s.m_c.Get(i,j));
|
|
s.m_effectivect.Set(i,s.m_ct[i]);
|
|
}
|
|
//--- calculation
|
|
for(i=0; i<n; i++)
|
|
{
|
|
//--- check
|
|
if(s.m_states[i]>=0)
|
|
{
|
|
for(k=0; k<n*n; k++)
|
|
s.m_effectivec.Set(ccnt,k,0);
|
|
for(k=0; k<n; k++)
|
|
s.m_effectivec.Set(ccnt,k*n+i,1);
|
|
//--- change values
|
|
s.m_effectivec.Set(ccnt,n*n,1.0);
|
|
s.m_effectivect.Set(ccnt,0);
|
|
ccnt++;
|
|
}
|
|
}
|
|
//--- create optimizer
|
|
for(i=0; i<n; i++)
|
|
for(j=0; j<n; j++)
|
|
s.m_tmpp.Set(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; i++)
|
|
for(j=0; j<n; j++)
|
|
{
|
|
double delt=s.m_bs.m_x[i*n+j]-s.m_priorp.Get(i,j);
|
|
s.m_bs.m_f+=vv*CMath::Sqr(delt);
|
|
s.m_bs.m_g.Set(i*n+j,2*vv*delt);
|
|
}
|
|
//--- calculate prediction error/gradient for K-th pair
|
|
for(k=0; k<npairs; k++)
|
|
{
|
|
for(i=0; i<n; i++)
|
|
{
|
|
i1_=-(i*n);
|
|
v=0.0;
|
|
for(i_=i*n; i_<i*n+n; i_++)
|
|
v+=s.m_bs.m_x[i_]*s.m_data.Get(k,i_+i1_);
|
|
vv=s.m_effectivew[i];
|
|
s.m_bs.m_f+=CMath::Sqr(vv*(v-s.m_data.Get(k,n+i)));
|
|
for(j=0; j<n; j++)
|
|
s.m_bs.m_g.Add(i*n+j,2*vv*vv*(v-s.m_data.Get(k,n+i))*s.m_data.Get(k,j));
|
|
}
|
|
}
|
|
//--- continue
|
|
continue;
|
|
}
|
|
}
|
|
//--- function call
|
|
CMinBLEIC::MinBLEICResultsBuf(s.m_bs,s.m_tmpp,s.m_br);
|
|
for(i=0; i<n; i++)
|
|
{
|
|
for(j=0; j<n; j++)
|
|
s.m_p.Set(i,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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMarkovCPD::MCPDResults(CMCPDState &s,CMatrixDouble &p,
|
|
CMCPDReport &rep)
|
|
{
|
|
//--- copy
|
|
p=s.m_p;
|
|
//--- 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 |
|
|
//+------------------------------------------------------------------+
|
|
void CMarkovCPD::MCPDInit(const int n,const int entrystate,
|
|
const int exitstate,CMCPDState &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=1,__FUNCTION__+": N<1"))
|
|
return;
|
|
//--- initialization
|
|
s.m_n=n;
|
|
//--- allocation
|
|
s.m_states.Resize(n);
|
|
s.m_states.Fill(0);
|
|
//--- check
|
|
if(entrystate>=0)
|
|
s.m_states.Set(entrystate,1);
|
|
//--- check
|
|
if(exitstate>=0)
|
|
s.m_states.Set(exitstate,-1);
|
|
//--- initialization
|
|
s.m_npairs=0;
|
|
s.m_regterm=1.0E-8;
|
|
s.m_ccnt=0;
|
|
//--- allocation
|
|
s.m_p=matrix<double>::Zeros(n,n);
|
|
s.m_ec=matrix<double>::Full(n,n,CInfOrNaN::NaN());
|
|
s.m_bndl=matrix<double>::Full(n,n,CInfOrNaN::NegativeInfinity());
|
|
s.m_bndu=matrix<double>::Full(n,n,CInfOrNaN::PositiveInfinity());
|
|
s.m_pw=vector<double>::Zeros(n);
|
|
s.m_priorp=matrix<double>::Identity(n,n);
|
|
s.m_tmpp=vector<double>::Zeros(n*n);
|
|
s.m_effectivew.Resize(n);
|
|
s.m_effectivebndl.Resize(n*n);
|
|
s.m_effectivebndu.Resize(n*n);
|
|
s.m_h.Resize(n*n);
|
|
//--- allocation
|
|
s.m_data=matrix<double>::Zeros(1,2*n);
|
|
//--- function call
|
|
CMinBLEIC::MinBLEICCreate(n*n,s.m_tmpp,s.m_bs);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Training report: |
|
|
//| * RelCLSError - fraction of misclassified cases. |
|
|
//| * AvgCE - acerage cross-entropy |
|
|
//| * RMSError - root-mean-square error |
|
|
//| * AvgError - average error |
|
|
//| * AvgRelError - average relative error |
|
|
//| * NGrad - number of gradient calculations |
|
|
//| * NHess - number of Hessian calculations |
|
|
//| * NCholesky - number of Cholesky decompositions |
|
|
//| NOTE 1: RelCLSError/AvgCE are zero on regression problems. |
|
|
//| NOTE 2: on classification problems RMSError/AvgError/AvgRelError |
|
|
//| contain errors in prediction of posterior probabilities |
|
|
//+------------------------------------------------------------------+
|
|
class CMLPReport
|
|
{
|
|
public:
|
|
//--- variables
|
|
double m_RelCLSError;
|
|
double m_AvgCE;
|
|
double m_RMSError;
|
|
double m_AvgError;
|
|
double m_AvgRelError;
|
|
int m_ngrad;
|
|
int m_nhess;
|
|
int m_ncholesky;
|
|
//--- constructor, destructor
|
|
CMLPReport(void) { ZeroMemory(this); }
|
|
~CMLPReport(void) {}
|
|
//--- copy
|
|
void Copy(const CMLPReport &obj);
|
|
//--- overloading
|
|
void operator=(const CMLPReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPReport::Copy(const CMLPReport &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_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) { m_innerobj.Copy(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);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable ngrad |
|
|
//+------------------------------------------------------------------+
|
|
int CMLPReportShell::GetNGrad(void)
|
|
{
|
|
return(m_innerobj.m_ngrad);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable ngrad |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPReportShell::SetNGrad(const int i)
|
|
{
|
|
m_innerobj.m_ngrad=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable nhess |
|
|
//+------------------------------------------------------------------+
|
|
int CMLPReportShell::GetNHess(void)
|
|
{
|
|
return(m_innerobj.m_nhess);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable nhess |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPReportShell::SetNHess(const int i)
|
|
{
|
|
m_innerobj.m_nhess=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable ncholesky |
|
|
//+------------------------------------------------------------------+
|
|
int CMLPReportShell::GetNCholesky(void)
|
|
{
|
|
return(m_innerobj.m_ncholesky);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable ncholesky |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPReportShell::SetNCholesky(const int i)
|
|
{
|
|
m_innerobj.m_ncholesky=i;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMLPReport *CMLPReportShell::GetInnerObj(void)
|
|
{
|
|
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) { ZeroMemory(this); }
|
|
~CMLPCVReport(void) {}
|
|
//--- copy
|
|
void Copy(const CMLPCVReport &obj);
|
|
//--- overloading
|
|
void operator=(const CMLPCVReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPCVReport::Copy(const 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) { m_innerobj.Copy(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);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable relclserror |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPCVReportShell::GetRelClsError(void)
|
|
{
|
|
return(m_innerobj.m_RelCLSError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable relclserror |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPCVReportShell::SetRelClsError(const double d)
|
|
{
|
|
m_innerobj.m_RelCLSError=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgce |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPCVReportShell::GetAvgCE(void)
|
|
{
|
|
return(m_innerobj.m_AvgCE);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgce |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPCVReportShell::SetAvgCE(const double d)
|
|
{
|
|
m_innerobj.m_AvgCE=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPCVReportShell::GetRMSError(void)
|
|
{
|
|
return(m_innerobj.m_RMSError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable rmserror |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPCVReportShell::SetRMSError(const double d)
|
|
{
|
|
m_innerobj.m_RMSError=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPCVReportShell::GetAvgError(void)
|
|
{
|
|
return(m_innerobj.m_AvgError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgerror |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPCVReportShell::SetAvgError(const double d)
|
|
{
|
|
m_innerobj.m_AvgError=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Returns the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPCVReportShell::GetAvgRelError(void)
|
|
{
|
|
return(m_innerobj.m_AvgRelError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing the value of the variable avgrelerror |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPCVReportShell::SetAvgRelError(const double d)
|
|
{
|
|
m_innerobj.m_AvgRelError=d;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return object of class |
|
|
//+------------------------------------------------------------------+
|
|
CMLPCVReport *CMLPCVReportShell::GetInnerObj(void)
|
|
{
|
|
return(GetPointer(m_innerobj));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Neural networks ensemble |
|
|
//+------------------------------------------------------------------+
|
|
class CMLPEnsemble
|
|
{
|
|
public:
|
|
//--- variables
|
|
int m_ensemblesize;
|
|
CMultilayerPerceptron m_network;
|
|
//--- arrays
|
|
CRowDouble m_weights;
|
|
CRowDouble m_columnmeans;
|
|
CRowDouble m_columnsigmas;
|
|
CRowDouble m_y;
|
|
//--- constructor, destructor
|
|
CMLPEnsemble(void) { m_ensemblesize=0; }
|
|
~CMLPEnsemble(void) {}
|
|
//--- copy
|
|
void Copy(const CMLPEnsemble &obj);
|
|
//--- overloading
|
|
void operator=(const CMLPEnsemble &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPEnsemble::Copy(const CMLPEnsemble &obj)
|
|
{
|
|
//--- copy variables
|
|
m_ensemblesize=obj.m_ensemblesize;
|
|
CMLPBase::MLPCopy(obj.m_network,m_network);
|
|
//--- copy arrays
|
|
m_weights=obj.m_weights;
|
|
m_columnmeans=obj.m_columnmeans;
|
|
m_columnsigmas=obj.m_columnsigmas;
|
|
m_y=obj.m_y;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Neural networks ensemble |
|
|
//+------------------------------------------------------------------+
|
|
class CMLPEnsembleShell
|
|
{
|
|
private:
|
|
CMLPEnsemble m_innerobj;
|
|
|
|
public:
|
|
//--- constructors, destructor
|
|
CMLPEnsembleShell(void) {}
|
|
CMLPEnsembleShell(CMLPEnsemble &obj) { m_innerobj.Copy(obj); }
|
|
~CMLPEnsembleShell(void) {}
|
|
//--- method
|
|
CMLPEnsemble *GetInnerObj(void) { return(GetPointer(m_innerobj)); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Temporary data structures used by following functions: |
|
|
//| * TrainNetworkX |
|
|
//| * StartTrainingX |
|
|
//| * ContinueTrainingX |
|
|
//| This structure contains: |
|
|
//| * network being trained |
|
|
//| * fully initialized LBFGS optimizer (we have to call |
|
|
//| MinLBFGSRestartFrom() before using it; usually it is done |
|
|
//| by StartTrainingX() function). |
|
|
//| * additional temporary arrays |
|
|
//| This structure should be initialized with InitMLPTrnSession() |
|
|
//| call. |
|
|
//+------------------------------------------------------------------+
|
|
struct CSMLPTrnSession
|
|
{
|
|
CRowDouble m_bestparameters;
|
|
double m_bestrmserror;
|
|
bool m_randomizenetwork;
|
|
CMultilayerPerceptron m_network;
|
|
CMinLBFGSState m_optimizer;
|
|
CMinLBFGSReport m_optimizerrep;
|
|
CRowDouble m_wbuf0;
|
|
CRowDouble m_wbuf1;
|
|
CRowInt m_allminibatches;
|
|
CRowInt m_currentminibatch;
|
|
RCommState m_rstate;
|
|
int m_algoused;
|
|
int m_minibatchsize;
|
|
CMLPReport m_mlprep;
|
|
CRowInt m_trnsubset;
|
|
CRowInt m_valsubset;
|
|
CHighQualityRandState m_generator;
|
|
//--- constructor / destructor
|
|
CSMLPTrnSession(void);
|
|
~CSMLPTrnSession(void) {}
|
|
//---
|
|
void Copy(const CSMLPTrnSession &obj);
|
|
//--- overloading
|
|
void operator=(const CSMLPTrnSession &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Coonstructor |
|
|
//+------------------------------------------------------------------+
|
|
CSMLPTrnSession::CSMLPTrnSession(void)
|
|
{
|
|
m_bestrmserror=0;
|
|
m_randomizenetwork=false;
|
|
m_algoused=0;
|
|
m_minibatchsize=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CSMLPTrnSession::Copy(const CSMLPTrnSession &obj)
|
|
{
|
|
m_bestparameters=obj.m_bestparameters;
|
|
m_bestrmserror=obj.m_bestrmserror;
|
|
m_randomizenetwork=obj.m_randomizenetwork;
|
|
m_network=obj.m_network;
|
|
m_optimizer=obj.m_optimizer;
|
|
m_optimizerrep=obj.m_optimizerrep;
|
|
m_wbuf0=obj.m_wbuf0;
|
|
m_wbuf1=obj.m_wbuf1;
|
|
m_allminibatches=obj.m_allminibatches;
|
|
m_currentminibatch=obj.m_currentminibatch;
|
|
m_rstate=obj.m_rstate;
|
|
m_algoused=obj.m_algoused;
|
|
m_minibatchsize=obj.m_minibatchsize;
|
|
m_mlprep=obj.m_mlprep;
|
|
m_trnsubset=obj.m_trnsubset;
|
|
m_valsubset=obj.m_valsubset;
|
|
m_generator=obj.m_generator;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Trainer object for neural network. |
|
|
//| You should not try to access fields of this object directly - use|
|
|
//| ALGLIB functions to work with this object. |
|
|
//+------------------------------------------------------------------+
|
|
struct CMLPTrainer
|
|
{
|
|
int m_nin;
|
|
int m_nout;
|
|
bool m_rcpar;
|
|
int m_lbfgsfactor;
|
|
double m_decay;
|
|
double m_wstep;
|
|
int m_maxits;
|
|
int m_datatype;
|
|
int m_npoints;
|
|
CMatrixDouble m_densexy;
|
|
CSparseMatrix m_sparsexy;
|
|
CSMLPTrnSession m_session;
|
|
int m_ngradbatch;
|
|
CRowInt m_subset;
|
|
int m_subsetsize;
|
|
CRowInt m_valsubset;
|
|
int m_valsubsetsize;
|
|
int m_algokind;
|
|
int m_minibatchsize;
|
|
//--- constructor / destructor
|
|
CMLPTrainer(void);
|
|
~CMLPTrainer(void) {}
|
|
void Copy(const CMLPTrainer &obj);
|
|
//--- overloading
|
|
void operator=(const CMLPTrainer &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CMLPTrainer::CMLPTrainer(void)
|
|
{
|
|
m_nin=0;
|
|
m_nout=0;
|
|
m_rcpar=false;
|
|
m_lbfgsfactor=0;
|
|
m_decay=0;
|
|
m_wstep=0;
|
|
m_maxits=0;
|
|
m_datatype=0;
|
|
m_npoints=0;
|
|
m_ngradbatch=0;
|
|
m_subsetsize=0;
|
|
m_valsubsetsize=0;
|
|
m_algokind=0;
|
|
m_minibatchsize=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrainer::Copy(const CMLPTrainer &obj)
|
|
{
|
|
m_nin=obj.m_nin;
|
|
m_nout=obj.m_nout;
|
|
m_rcpar=obj.m_rcpar;
|
|
m_lbfgsfactor=obj.m_lbfgsfactor;
|
|
m_decay=obj.m_decay;
|
|
m_wstep=obj.m_wstep;
|
|
m_maxits=obj.m_maxits;
|
|
m_datatype=obj.m_datatype;
|
|
m_npoints=obj.m_npoints;
|
|
m_densexy=obj.m_densexy;
|
|
m_sparsexy=obj.m_sparsexy;
|
|
m_session=obj.m_session;
|
|
m_ngradbatch=obj.m_ngradbatch;
|
|
m_subset=obj.m_subset;
|
|
m_subsetsize=obj.m_subsetsize;
|
|
m_valsubset=obj.m_valsubset;
|
|
m_valsubsetsize=obj.m_valsubsetsize;
|
|
m_algokind=obj.m_algokind;
|
|
m_minibatchsize=obj.m_minibatchsize;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Training neural networks |
|
|
//+------------------------------------------------------------------+
|
|
class CMLPTrain
|
|
{
|
|
public:
|
|
//--- constant
|
|
static const double m_mindecay;
|
|
static const int m_defaultlbfgsfactor;
|
|
//--- 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,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);
|
|
static void MLPCreateTrainer(int nin,int nout,CMLPTrainer &s);
|
|
static void MLPCreateTrainerCls(int nin,int nclasses,CMLPTrainer &s);
|
|
static void MLPSetDataset(CMLPTrainer &s,CMatrixDouble &xy,int npoints);
|
|
static void MLPSetSparseDataset(CMLPTrainer &s,CSparseMatrix &xy,int npoints);
|
|
static void MLPSetDecay(CMLPTrainer &s,double decay);
|
|
static void MLPSetCond(CMLPTrainer &s,double wstep,int maxits);
|
|
static void MLPSetAlgoBatch(CMLPTrainer &s);
|
|
static void MLPTrainNetwork(CMLPTrainer &s,CMultilayerPerceptron &network,int nrestarts,CMLPReport &rep);
|
|
static void MLPStartTraining(CMLPTrainer &s,CMultilayerPerceptron &network,bool randomstart);
|
|
static bool MLPContinueTraining(CMLPTrainer &s,CMultilayerPerceptron &network);
|
|
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);
|
|
static void MLPTrainEnsembleES(CMLPTrainer &s,CMLPEnsemble &ensemble,int nrestarts,CMLPReport &rep);
|
|
|
|
private:
|
|
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,CRowInt &folds);
|
|
static void MLPTrainNetworkX(CMLPTrainer &s,int nrestarts,int algokind,CRowInt &trnsubset,int trnsubsetsize,CRowInt &valsubset,int valsubsetsize,CMultilayerPerceptron &network,CMLPReport &rep);
|
|
static void MLPTrainEnsembleX(CMLPTrainer &s,CMLPEnsemble &ensemble,int idx0,int idx1,int nrestarts,int trainingmethod,int ngrad);
|
|
static void MLPStartTrainingX(CMLPTrainer &s,bool randomstart,int algokind,CRowInt &subset,int subsetsize,CSMLPTrnSession &session);
|
|
static bool MLPContinueTrainingX(CMLPTrainer &s,CRowInt &subset,int subsetsize,int &ngradbatch,CSMLPTrnSession &session);
|
|
static bool MLPContinueTrainingX_lbl1(CMLPTrainer &s,CRowInt &subset,int subsetsize,int &ngradbatch,CSMLPTrnSession &session,double &decay);
|
|
static bool MLPContinueTrainingX_lbl3(CMLPTrainer &s,CRowInt &subset,int subsetsize,int &ngradbatch,CSMLPTrnSession &session,double &decay);
|
|
static void MLPEBaggingInternal(CMLPEnsemble &ensemble,CMatrixDouble &xy,int npoints,double decay,int restarts,double wstep,int maxits,bool lmalgorithm,int &info,CMLPReport &rep,CMLPCVReport &ooberrors);
|
|
static void InitMLPTrnSession(CMultilayerPerceptron &networktrained,bool randomizenetwork,CMLPTrainer &trainer,CSMLPTrnSession &session);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize constant |
|
|
//+------------------------------------------------------------------+
|
|
const double CMLPTrain::m_mindecay=0.001;
|
|
const int CMLPTrain::m_defaultlbfgsfactor=6;
|
|
//+------------------------------------------------------------------+
|
|
//| 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 |
|
|
//+------------------------------------------------------------------+
|
|
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
|
|
CRowDouble g;
|
|
vector<double> d;
|
|
vector<double> x;
|
|
vector<double> y;
|
|
CRowDouble wbase;
|
|
CRowDouble wdir;
|
|
CRowDouble wt;
|
|
vector<double> wx;
|
|
CRowDouble 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))
|
|
{
|
|
y=xy.Col(nin);
|
|
//--- check
|
|
if((int)MathRound(y.Min())<0 || (int)MathRound(y.Max())>=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
|
|
g.Resize(wcount);
|
|
h.Resize(wcount,wcount);
|
|
hmod.Resize(wcount,wcount);
|
|
wbase.Resize(wcount);
|
|
wdir.Resize(wcount);
|
|
wbest.Resize(wcount);
|
|
wt.Resize(wcount);
|
|
wx.Resize(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
|
|
wbase=network.m_weights;
|
|
//--- 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
|
|
network.m_weights=state.m_x;
|
|
//--- function call
|
|
CMLPBase::MLPGradBatch(network,xy,npoints,state.m_f,state.m_g);
|
|
//--- weight decay
|
|
v=network.m_weights.Dot(network.m_weights);
|
|
state.m_f+=0.5*decay*v;
|
|
state.m_g+=network.m_weights*decay+0;
|
|
//--- next iteration
|
|
rep.m_ngrad++;
|
|
}
|
|
//--- function call
|
|
CMinLBFGS::MinLBFGSResults(state,wbase,internalrep);
|
|
network.m_weights=wbase;
|
|
//--- 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=network.m_weights.Dot(network.m_weights);
|
|
//--- change values
|
|
e=e+0.5*decay*v;
|
|
g+=network.m_weights*decay+0;
|
|
h.Diag(h.Diag()+decay);
|
|
//--- change values
|
|
rep.m_nhess++;
|
|
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.
|
|
hmod=h;
|
|
hmod.Diag(hmod.Diag(0)+lambdav);
|
|
//--- function call
|
|
spd=CTrFac::SPDMatrixCholesky(hmod,wcount,true);
|
|
rep.m_ncholesky++;
|
|
//--- check
|
|
if(!spd)
|
|
{
|
|
lambdav=lambdav*lambdaup*nu;
|
|
nu*=2;
|
|
continue;
|
|
}
|
|
//--- function call
|
|
CDenseSolver::SPDMatrixCholeskySolve(hmod,wcount,true,g,solverinfo,solverrep,wdir);
|
|
//--- check
|
|
if(solverinfo<0)
|
|
{
|
|
lambdav=lambdav*lambdaup*nu;
|
|
nu*=2;
|
|
continue;
|
|
}
|
|
wdir*=(-1);
|
|
//--- Lambda found.
|
|
//--- 1. Save old w in WBase
|
|
//--- 1. Test some stopping criterions
|
|
//--- 2. If error(w+wdir)>error(w),increase lambda
|
|
network.m_weights+=wdir;
|
|
xnorm2=network.m_weights.Dot(network.m_weights);
|
|
//--- change value
|
|
stepnorm=MathSqrt(wdir.Dot(wdir));
|
|
//--- 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*=lambdaup*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
|
|
wbase=network.m_weights;
|
|
wt=vector<double>::Zeros(wcount);
|
|
//--- function calls
|
|
CMinLBFGS::MinLBFGSCreateX(wcount,wcount,wt,1,0.0,state);
|
|
CMinLBFGS::MinLBFGSSetCond(state,0,0,0,5);
|
|
while(CMinLBFGS::MinLBFGSIteration(state))
|
|
{
|
|
//--- gradient
|
|
network.m_weights=wbase.ToVector()+state.m_x.MatMul(hmod.Transpose()+0);
|
|
//--- function call
|
|
CMLPBase::MLPGradBatch(network,xy,npoints,state.m_f,g);
|
|
state.m_g=g.MatMul(hmod)+0;
|
|
//--- weight decay
|
|
//--- grad(x'*x)=A'*(x0+A*t)
|
|
v=network.m_weights.Dot(network.m_weights);
|
|
state.m_f+=0.5*decay*v;
|
|
for(i=0; i<wcount; i++)
|
|
{
|
|
v=decay*network.m_weights[i];
|
|
state.m_g+=hmod[i]*v;
|
|
}
|
|
//--- next iteration
|
|
rep.m_ngrad++;
|
|
}
|
|
//--- function call
|
|
CMinLBFGS::MinLBFGSResults(state,wt,internalrep);
|
|
//--- Accept new position.
|
|
//--- Calculate Hessian
|
|
network.m_weights=wbase.ToVector()+wt.MatMul(hmod.Transpose()+0);
|
|
//--- function call
|
|
CMLPBase::MLPHessianBatch(network,xy,npoints,e,g,h);
|
|
v=network.m_weights.Dot(network.m_weights);
|
|
//--- change value
|
|
e+=0.5*decay*v;
|
|
g+=network.m_weights*decay+0;
|
|
h.Diag(h.Diag()+decay);
|
|
rep.m_nhess++;
|
|
//--- Update lambda
|
|
lambdav=lambdav*lambdadown;
|
|
nu=2;
|
|
}
|
|
//--- update WBest
|
|
v=network.m_weights.Dot(network.m_weights);
|
|
//--- change value
|
|
e=0.5*decay*v+CMLPBase::MLPError(network,xy,npoints);
|
|
//--- check
|
|
if(e<ebest)
|
|
{
|
|
ebest=e;
|
|
wbest=network.m_weights;
|
|
}
|
|
}
|
|
//--- copy WBest to output
|
|
network.m_weights=wbest;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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 |
|
|
//+------------------------------------------------------------------+
|
|
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 nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
double e=0;
|
|
double v=0;
|
|
double ebest=0;
|
|
//--- creating arrays
|
|
CRowDouble w;
|
|
CRowDouble wbest;
|
|
//--- create objects of classes
|
|
CMinLBFGSReport internalrep;
|
|
CMinLBFGSState state;
|
|
//--- 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))
|
|
{
|
|
w=xy.Col(nin)+0;
|
|
//--- check
|
|
if((int)MathRound(w.Min())<0 || (int)MathRound(w.Max())>=nout)
|
|
{
|
|
info=-2;
|
|
return;
|
|
}
|
|
}
|
|
//--- change values
|
|
decay=MathMax(decay,m_mindecay);
|
|
info=2;
|
|
//--- Prepare
|
|
CMLPBase::MLPInitPreprocessor(network,xy,npoints);
|
|
//--- allocation
|
|
wbest.Resize(wcount);
|
|
//--- initialization
|
|
ebest=CMath::m_maxrealnumber;
|
|
//--- Multiple starts
|
|
rep.m_ncholesky=0;
|
|
rep.m_nhess=0;
|
|
rep.m_ngrad=0;
|
|
for(int pass=1; pass<=restarts; pass++)
|
|
{
|
|
//--- Process
|
|
CMLPBase::MLPRandomize(network);
|
|
w=network.m_weights;
|
|
//--- function calls
|
|
CMinLBFGS::MinLBFGSCreate(wcount,(int)(MathMin(wcount,10)),w,state);
|
|
CMinLBFGS::MinLBFGSSetCond(state,0.0,0.0,wstep,maxits);
|
|
while(CMinLBFGS::MinLBFGSIteration(state))
|
|
{
|
|
network.m_weights=state.m_x;
|
|
//--- function call
|
|
CMLPBase::MLPGradNBatch(network,xy,npoints,state.m_f,state.m_g);
|
|
v=network.m_weights.Dot(network.m_weights);
|
|
state.m_f=state.m_f+0.5*decay*v;
|
|
state.m_g+=network.m_weights*decay+0;
|
|
rep.m_ngrad++;
|
|
}
|
|
//--- function call
|
|
CMinLBFGS::MinLBFGSResults(state,w,internalrep);
|
|
network.m_weights=w;
|
|
//--- Compare with best
|
|
v=network.m_weights.Dot(network.m_weights);
|
|
//--- change value
|
|
e=CMLPBase::MLPErrorN(network,xy,npoints)+0.5*decay*v;
|
|
//--- check
|
|
if(e<ebest)
|
|
{
|
|
wbest=network.m_weights;
|
|
ebest=e;
|
|
}
|
|
}
|
|
//--- The best network
|
|
network.m_weights=wbest;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPTrainES(CMultilayerPerceptron &network,
|
|
CMatrixDouble &trnxy,const int trnsize,
|
|
CMatrixDouble &valxy,const int valsize,
|
|
const double decay,int restarts,
|
|
int &info,CMLPReport &rep)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
double e=0;
|
|
double v=0;
|
|
double ebest=0;
|
|
int itbest=0;
|
|
int itcnt=0;
|
|
double wstep=0;
|
|
bool needrandomization=true;
|
|
//--- creating arrays
|
|
CRowDouble w;
|
|
CRowDouble wbest;
|
|
CRowDouble 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;
|
|
}
|
|
if(restarts==-1)
|
|
{
|
|
needrandomization=false;
|
|
restarts=1;
|
|
}
|
|
//--- function call
|
|
CMLPBase::MLPProperties(network,nin,nout,wcount);
|
|
//--- check
|
|
if(CMLPBase::MLPIsSoftMax(network))
|
|
{
|
|
w=trnxy.Col(nin)+0;
|
|
w.Resize(trnsize);
|
|
//--- check
|
|
if((int)MathRound(w.Min())<0 || (int)MathRound(w.Max())>=nout)
|
|
{
|
|
info=-2;
|
|
return;
|
|
}
|
|
w=valxy.Col(nin)+0;
|
|
w.Resize(valsize);
|
|
//--- check
|
|
if((int)MathRound(w.Min())<0 || (int)MathRound(w.Max())>=nout)
|
|
{
|
|
info=-2;
|
|
return;
|
|
}
|
|
}
|
|
//--- change value
|
|
info=2;
|
|
//--- Prepare
|
|
CMLPBase::MLPInitPreprocessor(network,trnxy,trnsize);
|
|
//--- initialization
|
|
wfinal=vector<double>::Zeros(wcount);
|
|
efinal=CMath::m_maxrealnumber;
|
|
//--- Multiple starts
|
|
rep.m_ncholesky=0;
|
|
rep.m_nhess=0;
|
|
rep.m_ngrad=0;
|
|
//--- calculation
|
|
for(int pass=1; pass<=restarts; pass++)
|
|
{
|
|
//--- Process
|
|
if(needrandomization)
|
|
CMLPBase::MLPRandomize(network);
|
|
//--- change values
|
|
ebest=CMLPBase::MLPError(network,valxy,valsize);
|
|
wbest=network.m_weights;
|
|
//--- change values
|
|
itbest=0;
|
|
w=network.m_weights;
|
|
//--- 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
|
|
if(state.m_needfg)
|
|
{
|
|
network.m_weights=state.m_x;
|
|
//--- function call
|
|
CMLPBase::MLPGradNBatch(network,trnxy,trnsize,state.m_f,state.m_g);
|
|
v=network.m_weights.Dot(network.m_weights);
|
|
state.m_f+=0.5*decay*v;
|
|
state.m_g+=network.m_weights*decay+0;
|
|
rep.m_ngrad++;
|
|
}
|
|
//--- Validation set
|
|
if(state.m_xupdated)
|
|
{
|
|
network.m_weights=state.m_x;
|
|
//--- function call
|
|
e=CMLPBase::MLPError(network,valxy,valsize);
|
|
//--- check
|
|
if(e<ebest)
|
|
{
|
|
ebest=e;
|
|
wbest=network.m_weights;
|
|
itbest=internalrep.m_iterationscount;
|
|
}
|
|
//--- check
|
|
if(itcnt>30 && (double)(itcnt)>(double)(1.5*itbest))
|
|
{
|
|
info=6;
|
|
break;
|
|
}
|
|
itcnt++;
|
|
}
|
|
}
|
|
//--- function call
|
|
CMinLBFGS::MinLBFGSResults(state,w,internalrep);
|
|
//--- Compare with final answer
|
|
if(ebest<efinal)
|
|
{
|
|
wfinal=wbest;
|
|
efinal=ebest;
|
|
}
|
|
}
|
|
//--- The best network
|
|
network.m_weights=wfinal;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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 |
|
|
//+------------------------------------------------------------------+
|
|
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 |
|
|
//+------------------------------------------------------------------+
|
|
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);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Creation of the network trainer object for regression networks |
|
|
//| INPUT PARAMETERS: |
|
|
//| NIn - number of inputs, NIn>=1 |
|
|
//| NOut - number of outputs, NOut>=1 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - neural network trainer object. |
|
|
//| This structure can be used to train any regression network with |
|
|
//| NIn inputs and NOut outputs. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPCreateTrainer(int nin,int nout,CMLPTrainer &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(nin>=1,__FUNCTION__+": NIn<1."))
|
|
return;
|
|
if(!CAp::Assert(nout>=1,__FUNCTION__+": NOut<1."))
|
|
return;
|
|
//--- change value
|
|
s.m_nin=nin;
|
|
s.m_nout=nout;
|
|
s.m_rcpar=true;
|
|
s.m_lbfgsfactor=m_defaultlbfgsfactor;
|
|
s.m_decay=1.0E-6;
|
|
MLPSetCond(s,0,0);
|
|
s.m_datatype=0;
|
|
s.m_npoints=0;
|
|
MLPSetAlgoBatch(s);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Creation of the network trainer object for classification |
|
|
//| networks |
|
|
//| INPUT PARAMETERS: |
|
|
//| NIn - number of inputs, NIn>=1 |
|
|
//| NClasses - number of classes, NClasses>=2 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - neural network trainer object. |
|
|
//| This structure can be used to train any classification network |
|
|
//| with NIn inputs and NOut outputs. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPCreateTrainerCls(int nin,int nclasses,CMLPTrainer &s)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(nin>=1,__FUNCTION__+": NIn<1."))
|
|
return;
|
|
if(!CAp::Assert(nclasses>=2,__FUNCTION__+": NClasses<2."))
|
|
return;
|
|
|
|
s.m_nin=nin;
|
|
s.m_nout=nclasses;
|
|
s.m_rcpar=false;
|
|
s.m_lbfgsfactor=m_defaultlbfgsfactor;
|
|
s.m_decay=1.0E-6;
|
|
MLPSetCond(s,0,0);
|
|
s.m_datatype=0;
|
|
s.m_npoints=0;
|
|
MLPSetAlgoBatch(s);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets "current dataset" of the trainer object to one|
|
|
//| passed by user. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - trainer object |
|
|
//| XY - training set, see below for information on the |
|
|
//| training set format. This function checks |
|
|
//| correctness of the dataset (no NANs/INFs, class |
|
|
//| numbers are correct) and throws exception when |
|
|
//| incorrect dataset is passed. |
|
|
//| NPoints - points count, >=0. |
|
|
//| DATASET FORMAT: |
|
|
//| This function uses two different dataset formats - one for |
|
|
//| regression networks, another one for classification networks. |
|
|
//| For regression networks with NIn inputs and NOut outputs |
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+NOut) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, next NOut columns are |
|
|
//| outputs |
|
|
//| For classification networks with NIn inputs and NClasses clases |
|
|
//| following datasetformat is used: |
|
|
//| * dataset is given by NPoints*(NIn+1) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, last column stores class |
|
|
//| number (from 0 to NClasses-1). |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPSetDataset(CMLPTrainer &s,CMatrixDouble &xy,int npoints)
|
|
{
|
|
int ndim=0;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nin>=1,__FUNCTION__+": possible parameter S is not initialized or spoiled(S.NIn<=0)."))
|
|
return;
|
|
if(!CAp::Assert(npoints>=0,__FUNCTION__+": NPoint<0"))
|
|
return;
|
|
if(!CAp::Assert(npoints<=xy.Rows(),__FUNCTION__+": invalid size of matrix XY(NPoint more then rows of matrix XY)"))
|
|
return;
|
|
//--- change value
|
|
s.m_datatype=0;
|
|
s.m_npoints=npoints;
|
|
if(npoints==0)
|
|
return;
|
|
if(s.m_rcpar)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nout>=1,__FUNCTION__+": possible parameter S is not initialized or is spoiled(NOut<1 for regression)."))
|
|
return;
|
|
ndim=s.m_nin+s.m_nout;
|
|
//--- check
|
|
if(!CAp::Assert(ndim<=xy.Cols(),__FUNCTION__+": invalid size of matrix XY(too few columns in matrix XY)."))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,ndim),__FUNCTION__+": parameter XY contains Infinite or NaN."))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nout>=2,__FUNCTION__+": possible parameter S is not initialized or is spoiled(NClasses<2 for classifier)."))
|
|
return;
|
|
ndim=s.m_nin+1;
|
|
//--- check
|
|
if(!CAp::Assert(ndim<=xy.Cols(),__FUNCTION__+": invalid size of matrix XY(too few columns in matrix XY)."))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,ndim),__FUNCTION__+": parameter XY contains Infinite or NaN."))
|
|
return;
|
|
vector<double> temp=xy.Col(s.m_nin);
|
|
temp.Resize(npoints);
|
|
//--- check
|
|
if(!CAp::Assert((int)MathRound(temp.Min())>=0 && (int)MathRound(temp.Max())<s.m_nout,__FUNCTION__+": invalid parameter XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses)."))
|
|
return;
|
|
}
|
|
s.m_densexy=xy;
|
|
s.m_densexy.Resize(npoints,ndim);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets "current dataset" of the trainer object to one|
|
|
//| passed by user (sparse matrix is used to store dataset). |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - trainer object |
|
|
//| XY - training set, see below for information on the |
|
|
//| training set format. This function checks |
|
|
//| correctness of the dataset (no NANs/INFs, class |
|
|
//| numbers are correct) and throws exception when |
|
|
//| incorrect dataset is passed. Any sparse storage |
|
|
//| format can be used: Hash-table, CRS... |
|
|
//| NPoints - points count, >=0 |
|
|
//| DATASET FORMAT: |
|
|
//| This function uses two different dataset formats - one for |
|
|
//| regression networks, another one for classification networks. |
|
|
//| For regression networks with NIn inputs and NOut outputs |
|
|
//| following dataset format is used: |
|
|
//| * dataset is given by NPoints*(NIn+NOut) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, next NOut columns are outputs |
|
|
//| For classification networks with NIn inputs and NClasses clases |
|
|
//| following datasetformat is used: |
|
|
//| * dataset is given by NPoints*(NIn+1) matrix |
|
|
//| * each row corresponds to one example |
|
|
//| * first NIn columns are inputs, last column stores class number|
|
|
//| (from 0 to NClasses-1). |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPSetSparseDataset(CMLPTrainer &s,CSparseMatrix &xy,int npoints)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
int t0=0;
|
|
int t1=0;
|
|
int i=0;
|
|
int j=0;
|
|
//--- Check correctness of the data
|
|
if(!CAp::Assert(s.m_nin>0,__FUNCTION__+": possible parameter S is not initialized or spoiled(S.NIn<=0)."))
|
|
return;
|
|
if(!CAp::Assert(npoints>=0,__FUNCTION__+": NPoint<0"))
|
|
return;
|
|
if(!CAp::Assert(npoints<=CSparse::SparseGetNRows(xy),__FUNCTION__+": invalid size of sparse matrix XY(NPoint more then rows of matrix XY)"))
|
|
return;
|
|
if(npoints>0)
|
|
{
|
|
t0=0;
|
|
t1=0;
|
|
if(s.m_rcpar)
|
|
{
|
|
if(!CAp::Assert(s.m_nout>=1,__FUNCTION__+": possible parameter S is not initialized or is spoiled(NOut<1 for regression)."))
|
|
return;
|
|
if(!CAp::Assert(s.m_nin+s.m_nout<=CSparse::SparseGetNCols(xy),__FUNCTION__+": invalid size of sparse matrix XY(too few columns in sparse matrix XY)."))
|
|
return;
|
|
//---
|
|
while(CSparse::SparseEnumerate(xy,t0,t1,i,j,v))
|
|
{
|
|
if(i<npoints && j<s.m_nin+s.m_nout)
|
|
if(!CAp::Assert(MathIsValidNumber(v),__FUNCTION__+": sparse matrix XY contains Infinite or NaN."))
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(!CAp::Assert(s.m_nout>=2,__FUNCTION__+": possible parameter S is not initialized or is spoiled(NClasses<2 for classifier)."))
|
|
return;
|
|
if(!CAp::Assert(s.m_nin+1<=CSparse::SparseGetNCols(xy),__FUNCTION__+": invalid size of sparse matrix XY(too few columns in sparse matrix XY)."))
|
|
return;
|
|
//---
|
|
while(CSparse::SparseEnumerate(xy,t0,t1,i,j,v))
|
|
{
|
|
if(i<npoints && j<=s.m_nin)
|
|
{
|
|
if(j!=s.m_nin)
|
|
{
|
|
if(!CAp::Assert(MathIsValidNumber(v),__FUNCTION__+": sparse matrix XY contains Infinite or NaN."))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if(!CAp::Assert((MathIsValidNumber(v) && (int)MathRound(v)>=0) && (int)MathRound(v)<s.m_nout,__FUNCTION__+": invalid sparse matrix XY(in classifier used nonexistent class number: either XY[.,NIn]<0 or XY[.,NIn]>=NClasses)."))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--- Set dataset
|
|
s.m_datatype=1;
|
|
s.m_npoints=npoints;
|
|
CSparse::SparseCopyToCRS(xy,s.m_sparsexy);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets weight decay coefficient which is used for |
|
|
//| training. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - trainer object |
|
|
//| Decay - weight decay coefficient, >=0. Weight decay term |
|
|
//| 'Decay*||Weights||^2' is added to error function. |
|
|
//| If you don't know what Decay to choose, use 1.0E-3.|
|
|
//| Weight decay can be set to zero, in this case |
|
|
//| network is trained without weight decay. |
|
|
//| NOTE: by default network uses some small nonzero value for weight|
|
|
//| decay. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPSetDecay(CMLPTrainer &s,double decay)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(decay),__FUNCTION__+": parameter Decay contains Infinite or NaN."))
|
|
return;
|
|
if(!CAp::Assert(decay>=0.0,__FUNCTION__+": Decay<0."))
|
|
return;
|
|
s.m_decay=decay;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets stopping criteria for the optimizer. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - trainer object |
|
|
//| WStep - stopping criterion. Algorithm stops if step size is|
|
|
//| less than WStep. Recommended value - 0.01. Zero |
|
|
//| step size means stopping after MaxIts iterations. |
|
|
//| WStep>=0. |
|
|
//| MaxIts - stopping criterion. Algorithm stops after MaxIts |
|
|
//| epochs (full passes over entire dataset). Zero |
|
|
//| MaxIts means stopping when step is sufficiently |
|
|
//| small. MaxIts>=0. |
|
|
//| NOTE: by default, WStep=0.005 and MaxIts=0 are used. These values|
|
|
//| are also used when MLPSetCond() is called with WStep=0 and |
|
|
//| MaxIts=0. |
|
|
//| NOTE: these stopping criteria are used for all kinds of neural |
|
|
//| training-from "conventional" networks to early stopping |
|
|
//| ensembles. When used for "conventional" networks, they are |
|
|
//| used as the only stopping criteria. When combined with |
|
|
//| early stopping, they used as ADDITIONAL stopping criteria |
|
|
//| which can terminate early stopping algorithm. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPSetCond(CMLPTrainer &s,double wstep,int maxits)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(wstep),__FUNCTION__+": parameter WStep contains Infinite or NaN."))
|
|
return;
|
|
if(!CAp::Assert(wstep>=0.0,__FUNCTION__+": WStep<0."))
|
|
return;
|
|
if(!CAp::Assert(maxits>=0,__FUNCTION__+": MaxIts<0."))
|
|
return;
|
|
|
|
if(wstep!=0.0 || maxits!=0)
|
|
{
|
|
s.m_wstep=wstep;
|
|
s.m_maxits=maxits;
|
|
}
|
|
else
|
|
{
|
|
s.m_wstep=0.005;
|
|
s.m_maxits=0;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets training algorithm: batch training using |
|
|
//| L-BFGS will be used. |
|
|
//| This algorithm: |
|
|
//| * the most robust for small-scale problems, but may be too slow|
|
|
//| for large scale ones. |
|
|
//| * perfoms full pass through the dataset before performing step |
|
|
//| * uses conditions specified by MLPSetCond() for stopping |
|
|
//| * is default one used by trainer object |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - trainer object |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPSetAlgoBatch(CMLPTrainer &s)
|
|
{
|
|
s.m_algokind=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function trains neural network passed to this function, |
|
|
//| using current dataset (one which was passed to MLPSetDataset() |
|
|
//| or MLPSetSparseDataset()) and current training settings. Training|
|
|
//| from NRestarts random starting positions is performed, best |
|
|
//| network is chosen. |
|
|
//| Training is performed using current training algorithm. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - trainer object |
|
|
//| Network - neural network. It must have same number of inputs |
|
|
//| and output/classes as was specified during creation|
|
|
//| of the trainer object. |
|
|
//| NRestarts- number of restarts, >=0: |
|
|
//| * NRestarts>0 means that specified number of random|
|
|
//| restarts are performed, best network is chosen |
|
|
//| after training |
|
|
//| * NRestarts=0 means that current state of the |
|
|
//| network is used for training. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Network - trained network |
|
|
//| NOTE: when no dataset was specified with MLPSetDataset / |
|
|
//| SetSparseDataset(), network is filled by zero values. Same |
|
|
//| behavior for functions MLPStartTraining and |
|
|
//| MLPContinueTraining. |
|
|
//| NOTE: this method uses sum-of-squares error function for training|
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPTrainNetwork(CMLPTrainer &s,CMultilayerPerceptron &network,
|
|
int nrestarts,CMLPReport &rep)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int ntype=0;
|
|
int ttype=0;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": parameter S is not initialized or is spoiled(S.NPoints<0)"))
|
|
return;
|
|
if(!CMLPBase::MLPIsSoftMax(network))
|
|
ntype=0;
|
|
else
|
|
ntype=1;
|
|
if(s.m_rcpar)
|
|
ttype=0;
|
|
else
|
|
ttype=1;
|
|
//--- check
|
|
if(!CAp::Assert(ntype==ttype,__FUNCTION__+": type of input network is not similar to network type in trainer object"))
|
|
return;
|
|
CMLPBase::MLPProperties(network,nin,nout,wcount);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": number of inputs in trainer is not equal to number of inputs in network"))
|
|
return;
|
|
if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": number of outputs in trainer is not equal to number of outputs in network"))
|
|
return;
|
|
if(!CAp::Assert(nrestarts>=0,__FUNCTION__+": NRestarts<0."))
|
|
return;
|
|
//--- Train
|
|
MLPTrainNetworkX(s,nrestarts,-1,s.m_subset,-1,s.m_subset,0,network,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| IMPORTANT: this is an "expert" version of the MLPTrain() function|
|
|
//| We do not recommend you to use it unless you are |
|
|
//| pretty sure that you need ability to monitor training |
|
|
//| progress. |
|
|
//| This function performs step-by-step training of the neural |
|
|
//| network. Here "step-by-step" means that training starts with |
|
|
//| MLPStartTraining() call, and then user subsequently calls |
|
|
//| MLPContinueTraining() to perform one more iteration of the |
|
|
//| training. |
|
|
//| After call to this function trainer object remembers network and |
|
|
//| is ready to train it. However, no training is performed until |
|
|
//| first call to MLPContinueTraining() function. Subsequent calls |
|
|
//| to MLPContinueTraining() will advance training progress one |
|
|
//| iteration further. |
|
|
//| EXAMPLE: |
|
|
//| > |
|
|
//| > ...initialize network and trainer object.... |
|
|
//| > |
|
|
//| > MLPStartTraining(Trainer, Network, True) |
|
|
//| > while MLPContinueTraining(Trainer, Network) do |
|
|
//| > ...visualize training progress... |
|
|
//| > |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - trainer object |
|
|
//| Network - neural network. It must have same number of inputs |
|
|
//| and output/classes as was specified during creation|
|
|
//| of the trainer object. |
|
|
//| RandomStart - randomize network before training or not: |
|
|
//| * True means that network is randomized and its |
|
|
//| initial state (one which was passed to the |
|
|
//| trainer object) is lost. |
|
|
//| * False means that training is started from the |
|
|
//| current state of the network |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Network - neural network which is ready to training (weights |
|
|
//| are initialized, preprocessor is initialized using |
|
|
//| current training set) |
|
|
//| NOTE: this method uses sum-of-squares error function for training|
|
|
//| NOTE: it is expected that trainer object settings are NOT changed|
|
|
//| during step-by-step training, i.e. no one changes stopping |
|
|
//| criteria or training set during training. It is possible |
|
|
//| and there is no defense against such actions, but algorithm|
|
|
//| behavior in such cases is undefined and can be |
|
|
//| unpredictable. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPStartTraining(CMLPTrainer &s,CMultilayerPerceptron &network,
|
|
bool randomstart)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int ntype=0;
|
|
int ttype=0;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": parameter S is not initialized or is spoiled(S.NPoints<0)"))
|
|
return;
|
|
if(!CMLPBase::MLPIsSoftMax(network))
|
|
ntype=0;
|
|
else
|
|
ntype=1;
|
|
if(s.m_rcpar)
|
|
ttype=0;
|
|
else
|
|
ttype=1;
|
|
//--- check
|
|
if(!CAp::Assert(ntype==ttype,__FUNCTION__+": type of input network is not similar to network type in trainer object"))
|
|
return;
|
|
CMLPBase::MLPProperties(network,nin,nout,wcount);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": number of inputs in trainer is not equal to number of inputs in the network."))
|
|
return;
|
|
if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": number of outputs in trainer is not equal to number of outputs in the network."))
|
|
return;
|
|
//--- Initialize temporaries
|
|
InitMLPTrnSession(network,randomstart,s,s.m_session);
|
|
//--- Train network
|
|
MLPStartTrainingX(s,randomstart,-1,s.m_subset,-1,s.m_session);
|
|
//--- Update network
|
|
CMLPBase::MLPCopyTunableParameters(s.m_session.m_network,network);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| IMPORTANT: this is an "expert" version of the MLPTrain() function|
|
|
//| We do not recommend you to use it unless you are |
|
|
//| pretty sure that you need ability to monitor training |
|
|
//| progress. |
|
|
//| This function performs step-by-step training of the neural |
|
|
//| network. Here "step-by-step" means that training starts with |
|
|
//| MLPStartTraining() call, and then user subsequently calls |
|
|
//| MLPContinueTraining() to perform one more iteration of the |
|
|
//| training. |
|
|
//| This function performs one more iteration of the training and |
|
|
//| returns either True (training continues) or False (training |
|
|
//| stopped). In case True was returned, Network weights are updated |
|
|
//| according to the current state of the optimization progress. |
|
|
//| In case False was returned, no additional updates is performed |
|
|
//| (previous update of the network weights moved us to the final |
|
|
//| point, and no additional updates is needed). |
|
|
//| EXAMPLE: |
|
|
//| > |
|
|
//| > [initialize network and trainer object] |
|
|
//| > |
|
|
//| > MLPStartTraining(Trainer, Network, True) |
|
|
//| > while MLPContinueTraining(Trainer, Network) do |
|
|
//| > [visualize training progress] |
|
|
//| > |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - trainer object |
|
|
//| Network - neural network structure, which is used to store |
|
|
//| current state of the training process. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Network - weights of the neural network are rewritten by the |
|
|
//| current approximation. |
|
|
//| NOTE: this method uses sum-of-squares error function for training|
|
|
//| NOTE: it is expected that trainer object settings are NOT changed|
|
|
//| during step-by-step training, i.e. no one changes stopping |
|
|
//| criteria or training set during training. It is possible |
|
|
//| and there is no defense against such actions, but algorithm|
|
|
//| behavior in such cases is undefined and can be |
|
|
//| unpredictable. |
|
|
//| NOTE: It is expected that Network is the same one which was |
|
|
//| passed to MLPStartTraining() function. However, THIS |
|
|
//| function checks only following: |
|
|
//| * that number of network inputs is consistent with |
|
|
//| trainer object settings |
|
|
//| * that number of network outputs / classes is consistent |
|
|
//| with trainer object settings |
|
|
//| * that number of network weights is the same as number of|
|
|
//| weights in the network passed to MLPStartTraining() |
|
|
//| function Exception is thrown when these conditions are |
|
|
//| violated. |
|
|
//| It is also expected that you do not change state of the network |
|
|
//| on your own - the only party who has right to change network |
|
|
//| during its training is a trainer object. Any attempt to interfere|
|
|
//| with trainer may lead to unpredictable results. |
|
|
//+------------------------------------------------------------------+
|
|
bool CMLPTrain::MLPContinueTraining(CMLPTrainer &s,
|
|
CMultilayerPerceptron &network)
|
|
{
|
|
//--- create variables
|
|
bool result;
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int ntype=0;
|
|
int ttype=0;
|
|
int i_=0;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": parameter S is not initialized or is spoiled(S.NPoints<0)"))
|
|
return(false);
|
|
if(s.m_rcpar)
|
|
ttype=0;
|
|
else
|
|
ttype=1;
|
|
if(!CMLPBase::MLPIsSoftMax(network))
|
|
ntype=0;
|
|
else
|
|
ntype=1;
|
|
//--- check
|
|
if(!CAp::Assert(ntype==ttype,__FUNCTION__+": type of input network is not similar to network type in trainer object."))
|
|
return(false);
|
|
CMLPBase::MLPProperties(network,nin,nout,wcount);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": number of inputs in trainer is not equal to number of inputs in the network."))
|
|
return(false);
|
|
if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": number of outputs in trainer is not equal to number of outputs in the network."))
|
|
return(false);
|
|
result=MLPContinueTrainingX(s,s.m_subset,-1,s.m_ngradbatch,s.m_session);
|
|
if(result)
|
|
network.m_weights=s.m_session.m_network.m_weights;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal cross-validation subroutine |
|
|
//+------------------------------------------------------------------+
|
|
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 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;
|
|
//--- creating arrays
|
|
CRowInt folds;
|
|
CRowDouble x;
|
|
CRowDouble 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);
|
|
x.Resize(nin);
|
|
y.Resize(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(int fold=0; fold<=foldscount-1; fold++)
|
|
{
|
|
//--- Separate set
|
|
tssize=0;
|
|
cvssize=0;
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
//--- check
|
|
if(folds[i]==fold)
|
|
{
|
|
testset.Row(tssize,xy[i]+0);
|
|
tssize++;
|
|
}
|
|
else
|
|
{
|
|
cvset.Row(cvssize,xy[i]+0);
|
|
cvssize++;
|
|
}
|
|
}
|
|
//--- 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+=internalrep.m_ngrad;
|
|
rep.m_nhess+=internalrep.m_nhess;
|
|
rep.m_ncholesky+=internalrep.m_ncholesky;
|
|
//--- Estimate error using CV test set
|
|
if(CMLPBase::MLPIsSoftMax(network))
|
|
{
|
|
//--- classification-only code
|
|
cvrep.m_RelCLSError+=CMLPBase::MLPClsError(network,testset,tssize);
|
|
cvrep.m_AvgCE+=CMLPBase::MLPErrorN(network,testset,tssize);
|
|
}
|
|
//--- calculation
|
|
for(int i=0; i<=tssize-1; i++)
|
|
{
|
|
x=testset[i]+0;
|
|
x.Resize(nin);
|
|
//--- function call
|
|
CMLPBase::MLPProcess(network,x,y);
|
|
//--- check
|
|
if(CMLPBase::MLPIsSoftMax(network))
|
|
{
|
|
//--- Classification-specific code
|
|
k=(int)MathRound(testset.Get(i,nin));
|
|
for(int j=0; j<nout; j++)
|
|
{
|
|
//--- check
|
|
if(j==k)
|
|
{
|
|
//--- change values
|
|
cvrep.m_RMSError+=CMath::Sqr(y[j]-1);
|
|
cvrep.m_AvgError+=MathAbs(y[j]-1);
|
|
cvrep.m_AvgRelError+=MathAbs(y[j]-1);
|
|
relcnt++;
|
|
}
|
|
else
|
|
{
|
|
//--- change values
|
|
cvrep.m_RMSError+=CMath::Sqr(y[j]);
|
|
cvrep.m_AvgError+=MathAbs(y[j]);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Regression-specific code
|
|
for(int j=0; j<nout; j++)
|
|
{
|
|
cvrep.m_RMSError+=CMath::Sqr(y[j]-testset.Get(i,nin+j));
|
|
cvrep.m_AvgError=cvrep.m_AvgError+MathAbs(y[j]-testset.Get(i,nin+j));
|
|
//--- check
|
|
if(testset.Get(i,nin+j)!=0.0)
|
|
{
|
|
cvrep.m_AvgRelError+=MathAbs((y[j]-testset.Get(i,nin+j))/testset.Get(i,nin+j));
|
|
relcnt++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--- check
|
|
if(CMLPBase::MLPIsSoftMax(network))
|
|
{
|
|
cvrep.m_RelCLSError/=npoints;
|
|
cvrep.m_AvgCE/=(MathLog(2)*npoints);
|
|
}
|
|
//--- change values
|
|
cvrep.m_RMSError=MathSqrt(cvrep.m_RMSError/(npoints*nout));
|
|
cvrep.m_AvgError/=(npoints*nout);
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPKFoldSplit(CMatrixDouble &xy,const int npoints,
|
|
const int nclasses,const int foldscount,
|
|
const bool stratifiedsplits,CRowInt &folds)
|
|
{
|
|
//--- create variables
|
|
int j=0;
|
|
CHighQualityRandState rs;
|
|
//--- 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
|
|
folds.Resize(npoints);
|
|
CHighQualityRand::HQRndRandomize(rs);
|
|
for(int i=0; i<npoints; i++)
|
|
folds.Set(i,i*foldscount/npoints);
|
|
//--- calculation
|
|
for(int i=0; i<npoints-1; i++)
|
|
{
|
|
j=i+CHighQualityRand::HQRndUniformI(rs,npoints-i);
|
|
//--- check
|
|
if(j!=i)
|
|
folds.Swap(i,j);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function trains neural network passed to this function, |
|
|
//| using current dataset (one which was passed to MLPSetDataset() or|
|
|
//| MLPSetSparseDataset()) and current training settings. Training |
|
|
//| from NRestarts random starting positions is performed, best |
|
|
//| network is chosen. |
|
|
//| This function is inteded to be used internally. It may be used in|
|
|
//| several settings: |
|
|
//| *training with ValSubsetSize=0, corresponds to "normal" |
|
|
//| training with termination criteria based on S.MaxIts |
|
|
//| (steps count) and S.WStep (step size). Training sample is |
|
|
//| given by TrnSubset/TrnSubsetSize. |
|
|
//| * training with ValSubsetSize>0, corresponds to early stopping |
|
|
//| training with additional MaxIts/WStep stopping criteria. |
|
|
//| Training sample is given by TrnSubset/TrnSubsetSize, |
|
|
//| validation sample is given by ValSubset/ ValSubsetSize. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPTrainNetworkX(CMLPTrainer &s,
|
|
int nrestarts,
|
|
int algokind,
|
|
CRowInt &trnsubset,
|
|
int trnsubsetsize,
|
|
CRowInt &valsubset,
|
|
int valsubsetsize,
|
|
CMultilayerPerceptron &network,
|
|
CMLPReport &rep)
|
|
{
|
|
//--- create variables
|
|
CModelErrors modrep;
|
|
double eval=0;
|
|
double ebest=0;
|
|
int ngradbatch=0;
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int pcount=0;
|
|
int itbest=0;
|
|
int itcnt=0;
|
|
int ntype=0;
|
|
int ttype=0;
|
|
bool rndstart;
|
|
int nr0=0;
|
|
int nr1=0;
|
|
double bestrmserror=0;
|
|
CSMLPTrnSession psession;
|
|
//--- function call
|
|
CMLPBase::MLPProperties(network,nin,nout,wcount);
|
|
//--- Process root call
|
|
//--- Check correctness of parameters
|
|
if(!CAp::Assert(algokind==0 || algokind==-1,__FUNCTION__+": unexpected AlgoKind"))
|
|
return;
|
|
if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": internal error - parameter S is not initialized or is spoiled(S.NPoints<0)"))
|
|
return;
|
|
if(s.m_rcpar)
|
|
ttype=0;
|
|
else
|
|
ttype=1;
|
|
if(!CMLPBase::MLPIsSoftMax(network))
|
|
ntype=0;
|
|
else
|
|
ntype=1;
|
|
//--- check
|
|
if(!CAp::Assert(ntype==ttype,__FUNCTION__+": internal error - type of the training network is not similar to network type in trainer object"))
|
|
return;
|
|
if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": internal error - number of inputs in trainer is not equal to number of inputs in the training network."))
|
|
return;
|
|
if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": internal error - number of outputs in trainer is not equal to number of outputs in the training network."))
|
|
return;
|
|
if(!CAp::Assert(nrestarts>=0,__FUNCTION__+": internal error - NRestarts<0."))
|
|
return;
|
|
if(!CAp::Assert(trnsubset.Size()>=trnsubsetsize,__FUNCTION__+": internal error - parameter TrnSubsetSize more than input subset size(Length(TrnSubset)<TrnSubsetSize)"))
|
|
return;
|
|
if(!CAp::Assert(trnsubsetsize<=0 || (trnsubset.Min()>=0 && trnsubset.Max()<s.m_npoints),__FUNCTION__+": internal error - parameter TrnSubset contains incorrect index(TrnSubset[I]<0 or TrnSubset[I]>S.NPoints-1)"))
|
|
return;
|
|
if(!CAp::Assert(valsubset.Size()>=valsubsetsize,__FUNCTION__+": internal error - parameter ValSubsetSize more than input subset size(Length(ValSubset)<ValSubsetSize)"))
|
|
return;
|
|
if(!CAp::Assert(valsubsetsize<=0 || (valsubset.Min()>=0 && valsubset.Max()<s.m_npoints),__FUNCTION__+": internal error - parameter ValSubset contains incorrect index(ValSubset[I]<0 or ValSubset[I]>S.NPoints-1)"))
|
|
return;
|
|
//--- Train
|
|
//--- * NRestarts>=1 means that network is restarted from random position
|
|
//--- * NRestarts=0 means that network is not randomized
|
|
rndstart=nrestarts>0;
|
|
bestrmserror=CMath::m_maxrealnumber;
|
|
for(int iter=0; iter<=nrestarts; iter++)
|
|
{
|
|
rep.m_ngrad=0;
|
|
rep.m_nhess=0;
|
|
rep.m_ncholesky=0;
|
|
InitMLPTrnSession(network,rndstart,s,psession);
|
|
if((s.m_datatype==0 || s.m_datatype==1) && s.m_npoints>0 && trnsubsetsize!=0)
|
|
{
|
|
//--- Train network using combination of early stopping and step-size
|
|
//--- and step-count based criteria. Network state with best value of
|
|
//--- validation set error is stored in WBuf0. When validation set is
|
|
//--- zero, most recent state of network is stored.
|
|
ngradbatch=0;
|
|
eval=0;
|
|
ebest=0;
|
|
itbest=0;
|
|
itcnt=0;
|
|
MLPStartTrainingX(s,rndstart,algokind,trnsubset,trnsubsetsize,psession);
|
|
switch(s.m_datatype)
|
|
{
|
|
case 0:
|
|
ebest=CMLPBase::MLPErrorSubset(psession.m_network,s.m_densexy,s.m_npoints,valsubset,valsubsetsize);
|
|
break;
|
|
case 1:
|
|
ebest=CMLPBase::MLPErrorSparseSubset(psession.m_network,s.m_sparsexy,s.m_npoints,valsubset,valsubsetsize);
|
|
break;
|
|
}
|
|
psession.m_wbuf0=psession.m_network.m_weights;
|
|
while(MLPContinueTrainingX(s,trnsubset,trnsubsetsize,ngradbatch,psession))
|
|
{
|
|
switch(s.m_datatype)
|
|
{
|
|
case 0:
|
|
eval=CMLPBase::MLPErrorSubset(psession.m_network,s.m_densexy,s.m_npoints,valsubset,valsubsetsize);
|
|
break;
|
|
case 1:
|
|
eval=CMLPBase::MLPErrorSparseSubset(psession.m_network,s.m_sparsexy,s.m_npoints,valsubset,valsubsetsize);
|
|
break;
|
|
}
|
|
if(eval<=ebest || valsubsetsize==0)
|
|
{
|
|
psession.m_wbuf0=psession.m_network.m_weights;
|
|
ebest=eval;
|
|
itbest=itcnt;
|
|
}
|
|
if(itcnt>30 && (double)(itcnt)>(double)(1.5*itbest))
|
|
break;
|
|
itcnt++;
|
|
}
|
|
psession.m_network.m_weights=psession.m_wbuf0;
|
|
rep.m_ngrad=ngradbatch;
|
|
}
|
|
else
|
|
psession.m_network.m_weights.Fill(0);
|
|
//--- Evaluate network performance and update PSession.BestParameters/BestRMSError
|
|
//--- (if needed).
|
|
switch(s.m_datatype)
|
|
{
|
|
case 0:
|
|
CMLPBase::MLPAllErrorsSubset(psession.m_network,s.m_densexy,s.m_npoints,trnsubset,trnsubsetsize,modrep);
|
|
break;
|
|
case 1:
|
|
CMLPBase::MLPAllErrorsSparseSubset(psession.m_network,s.m_sparsexy,s.m_npoints,trnsubset,trnsubsetsize,modrep);
|
|
break;
|
|
}
|
|
if(modrep.m_RMSError<psession.m_bestrmserror)
|
|
{
|
|
CMLPBase::MLPExportTunableParameters(psession.m_network,psession.m_bestparameters,pcount);
|
|
psession.m_bestrmserror=modrep.m_RMSError;
|
|
}
|
|
//--- Choose best network
|
|
if(psession.m_bestrmserror<bestrmserror)
|
|
{
|
|
CMLPBase::MLPImportTunableParameters(network,psession.m_bestparameters);
|
|
bestrmserror=psession.m_bestrmserror;
|
|
}
|
|
}
|
|
//--- Calculate errors
|
|
switch(s.m_datatype)
|
|
{
|
|
case 0:
|
|
CMLPBase::MLPAllErrorsSubset(network,s.m_densexy,s.m_npoints,trnsubset,trnsubsetsize,modrep);
|
|
break;
|
|
case 1:
|
|
CMLPBase::MLPAllErrorsSparseSubset(network,s.m_sparsexy,s.m_npoints,trnsubset,trnsubsetsize,modrep);
|
|
break;
|
|
}
|
|
rep.m_RelCLSError=modrep.m_RelCLSError;
|
|
rep.m_AvgCE=modrep.m_AvgCE;
|
|
rep.m_RMSError=modrep.m_RMSError;
|
|
rep.m_AvgError=modrep.m_AvgError;
|
|
rep.m_AvgRelError=modrep.m_AvgRelError;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function trains neural network ensemble passed to this |
|
|
//| function using current dataset and early stopping training |
|
|
//| algorithm. Each early stopping round performs NRestarts random |
|
|
//| restarts (thus, EnsembleSize*NRestarts training rounds is |
|
|
//| performed in total). |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPTrainEnsembleX(CMLPTrainer &s,CMLPEnsemble &ensemble,
|
|
int idx0,
|
|
int idx1,
|
|
int nrestarts,
|
|
int trainingmethod,
|
|
int ngrad)
|
|
{
|
|
//--- create variables
|
|
int nin=CMLPBase::MLPGetInputsCount(ensemble.m_network);
|
|
int nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
|
|
int wcount=CMLPBase::MLPGetWeightsCount(ensemble.m_network);
|
|
int pcount=0;
|
|
int trnsubsetsize=0;
|
|
int valsubsetsize=0;
|
|
int k0=0;
|
|
int i1_=0;
|
|
CSMLPTrnSession psession;
|
|
CHighQualityRandState rs;
|
|
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
pcount=nin;
|
|
else
|
|
pcount=nin+nout;
|
|
if(nrestarts<=0)
|
|
nrestarts=1;
|
|
//--- Handle degenerate case
|
|
if(s.m_npoints<2)
|
|
{
|
|
ensemble.m_weights.Fill(0.0);
|
|
ensemble.m_columnmeans.Fill(0.0);
|
|
ensemble.m_columnsigmas.Fill(1.0);
|
|
return;
|
|
}
|
|
//--- Prepare:
|
|
//--- * prepare MLPETrnSessions
|
|
//--- * fill ensemble by zeros (helps to detect errors)
|
|
ensemble.m_weights.Fill(0.0);
|
|
ensemble.m_columnmeans.Fill(0.0);
|
|
ensemble.m_columnsigmas.Fill(0.0);
|
|
//--- Train
|
|
CHighQualityRand::HQRndRandomize(rs);
|
|
for(int k=idx0; k<idx1; k++)
|
|
{
|
|
InitMLPTrnSession(ensemble.m_network,true,s,psession);
|
|
//--- Split set
|
|
trnsubsetsize=0;
|
|
valsubsetsize=0;
|
|
if(trainingmethod==0)
|
|
{
|
|
psession.m_trnsubset.Resize(s.m_npoints);
|
|
psession.m_valsubset.Resize(s.m_npoints);
|
|
do
|
|
{
|
|
trnsubsetsize=0;
|
|
valsubsetsize=0;
|
|
for(int i=0; i<s.m_npoints; i++)
|
|
{
|
|
if(CMath::RandomReal()<0.66)
|
|
{
|
|
//--- Assign sample to training set
|
|
psession.m_trnsubset.Set(trnsubsetsize,i);
|
|
trnsubsetsize++;
|
|
}
|
|
else
|
|
{
|
|
//--- Assign sample to validation set
|
|
psession.m_valsubset.Set(valsubsetsize,i);
|
|
valsubsetsize++;
|
|
}
|
|
}
|
|
}
|
|
while(!(trnsubsetsize!=0 && valsubsetsize!=0));
|
|
psession.m_trnsubset.Resize(trnsubsetsize);
|
|
psession.m_valsubset.Resize(valsubsetsize);
|
|
}
|
|
if(trainingmethod==1)
|
|
{
|
|
valsubsetsize=0;
|
|
trnsubsetsize=s.m_npoints;
|
|
psession.m_trnsubset.Resize(trnsubsetsize);
|
|
psession.m_valsubset.Resize(valsubsetsize);
|
|
for(int i=0; i<s.m_npoints; i++)
|
|
psession.m_trnsubset.Set(i,CHighQualityRand::HQRndUniformI(rs,s.m_npoints));
|
|
}
|
|
//--- Train
|
|
MLPTrainNetworkX(s,nrestarts,-1,psession.m_trnsubset,trnsubsetsize,psession.m_valsubset,valsubsetsize,psession.m_network,psession.m_mlprep);
|
|
ngrad+=psession.m_mlprep.m_ngrad;
|
|
//--- Save results
|
|
i1_=-(k*wcount);
|
|
for(int i_=k*wcount; i_<(k+1)*wcount; i_++)
|
|
ensemble.m_weights.Set(i_,psession.m_network.m_weights[i_+i1_]);
|
|
i1_=-(k*pcount);
|
|
for(int i_=k*pcount; i_<(k+1)*pcount ; i_++)
|
|
{
|
|
ensemble.m_columnmeans.Set(i_,psession.m_network.m_columnmeans[i_+i1_]);
|
|
ensemble.m_columnsigmas.Set(i_,psession.m_network.m_columnsigmas[i_+i1_]);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs step-by-step training of the neural |
|
|
//| network. Here "step-by-step" means that training starts with |
|
|
//| MLPStartTrainingX call, and then user subsequently calls |
|
|
//| MLPContinueTrainingX to perform one more iteration of the |
|
|
//| training. |
|
|
//| After call to this function trainer object remembers network and |
|
|
//| is ready to train it. However, no training is performed until |
|
|
//| first call to MLPContinueTraining() function. Subsequent calls |
|
|
//| to MLPContinueTraining() will advance traing progress one |
|
|
//| iteration further. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPStartTrainingX(CMLPTrainer &s,bool randomstart,
|
|
int algokind,CRowInt &subset,
|
|
int subsetsize,
|
|
CSMLPTrnSession &session)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int ntype=0;
|
|
int ttype=0;
|
|
//--- Check parameters
|
|
if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": internal error - parameter S is not initialized or is spoiled(S.NPoints<0)"))
|
|
return;
|
|
if(!CAp::Assert(algokind==0 || algokind==-1,__FUNCTION__+": unexpected AlgoKind"))
|
|
return;
|
|
if(s.m_rcpar)
|
|
ttype=0;
|
|
else
|
|
ttype=1;
|
|
if(!CMLPBase::MLPIsSoftMax(session.m_network))
|
|
ntype=0;
|
|
else
|
|
ntype=1;
|
|
if(!CAp::Assert(ntype==ttype,__FUNCTION__+": internal error - type of the resulting network is not similar to network type in trainer object"))
|
|
return;
|
|
CMLPBase::MLPProperties(session.m_network,nin,nout,wcount);
|
|
if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": number of inputs in trainer is not equal to number of inputs in the network."))
|
|
return;
|
|
if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": number of outputs in trainer is not equal to number of outputs in the network."))
|
|
return;
|
|
if(!CAp::Assert(subset.Size()>=subsetsize,__FUNCTION__+": internal error - parameter SubsetSize more than input subset size(Length(Subset)<SubsetSize)"))
|
|
return;
|
|
if(!CAp::Assert(subsetsize<=0 || (subset.Min()>=0 && subset.Max()<s.m_npoints),__FUNCTION__+": internal error - parameter Subset contains incorrect index(Subset[I]<0 or Subset[I]>S.NPoints-1)"))
|
|
return;
|
|
//--- Prepare session
|
|
CMinLBFGS::MinLBFGSSetCond(session.m_optimizer,0.0,0.0,s.m_wstep,s.m_maxits);
|
|
if(s.m_npoints>0 && subsetsize!=0)
|
|
{
|
|
if(randomstart)
|
|
CMLPBase::MLPRandomize(session.m_network);
|
|
CMinLBFGS::MinLBFGSRestartFrom(session.m_optimizer,session.m_network.m_weights);
|
|
}
|
|
else
|
|
session.m_network.m_weights.Fill(0);
|
|
//---
|
|
if(algokind==-1)
|
|
{
|
|
session.m_algoused=s.m_algokind;
|
|
if(s.m_algokind==1)
|
|
session.m_minibatchsize=s.m_minibatchsize;
|
|
}
|
|
else
|
|
session.m_algoused=0;
|
|
CHighQualityRand::HQRndRandomize(session.m_generator);
|
|
session.m_rstate.ia.Resize(16);
|
|
session.m_rstate.ra.Resize(2);
|
|
session.m_rstate.stage=-1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs step-by-step training of the neural |
|
|
//| network. Here "step-by-step" means that training starts with |
|
|
//| MLPStartTrainingX call, and then user subsequently calls |
|
|
//| MLPContinueTrainingX to perform one more iteration of the |
|
|
//| training. |
|
|
//| This function performs one more iteration of the training and |
|
|
//| returns either True (training continues) or False (training |
|
|
//| stopped). In case True was returned, Network weights are updated |
|
|
//| according to the current state of the optimization progress. In |
|
|
//| case False was returned, no additional updates is performed |
|
|
//| (previous update of the network weights moved us to the final |
|
|
//| point, and no additional updates is needed). |
|
|
//| EXAMPLE: |
|
|
//| > |
|
|
//| > [initialize network and trainer object] |
|
|
//| > |
|
|
//| > MLPStartTraining(Trainer, Network, True) |
|
|
//| > while MLPContinueTraining(Trainer, Network) do |
|
|
//| > [visualize training progress] |
|
|
//| > |
|
|
//+------------------------------------------------------------------+
|
|
bool CMLPTrain::MLPContinueTrainingX(CMLPTrainer &s,CRowInt &subset,
|
|
int subsetsize,
|
|
int &ngradbatch,
|
|
CSMLPTrnSession &session)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int twcount=0;
|
|
int ntype=0;
|
|
int ttype=0;
|
|
double decay=0;
|
|
double v=0;
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int trnsetsize=0;
|
|
int epoch=0;
|
|
int minibatchcount=0;
|
|
int minibatchidx=0;
|
|
int cursize=0;
|
|
int idx0=0;
|
|
int idx1=0;
|
|
//--- Reverse communication preparations
|
|
//--- I know it looks ugly, but it works the same way
|
|
//--- anywhere from C++ to Python.
|
|
//--- This code initializes locals by:
|
|
//--- * random values determined during code
|
|
//--- generation - on first subroutine call
|
|
//--- * values from previous call - on subsequent calls
|
|
if(session.m_rstate.stage>=0)
|
|
{
|
|
nin=session.m_rstate.ia[0];
|
|
nout=session.m_rstate.ia[1];
|
|
wcount=session.m_rstate.ia[2];
|
|
twcount=session.m_rstate.ia[3];
|
|
ntype=session.m_rstate.ia[4];
|
|
ttype=session.m_rstate.ia[5];
|
|
i=session.m_rstate.ia[6];
|
|
j=session.m_rstate.ia[7];
|
|
k=session.m_rstate.ia[8];
|
|
trnsetsize=session.m_rstate.ia[9];
|
|
epoch=session.m_rstate.ia[10];
|
|
minibatchcount=session.m_rstate.ia[11];
|
|
minibatchidx=session.m_rstate.ia[12];
|
|
cursize=session.m_rstate.ia[13];
|
|
idx0=session.m_rstate.ia[14];
|
|
idx1=session.m_rstate.ia[15];
|
|
decay=session.m_rstate.ra[0];
|
|
v=session.m_rstate.ra[1];
|
|
}
|
|
else
|
|
{
|
|
nin=359;
|
|
nout=-58;
|
|
wcount=-919;
|
|
twcount=-909;
|
|
ntype=81;
|
|
ttype=255;
|
|
i=74;
|
|
j=-788;
|
|
k=809;
|
|
trnsetsize=205;
|
|
epoch=-838;
|
|
minibatchcount=939;
|
|
minibatchidx=-526;
|
|
cursize=763;
|
|
idx0=-541;
|
|
idx1=-698;
|
|
decay=-900;
|
|
v=-318;
|
|
}
|
|
if(session.m_rstate.stage==0)
|
|
{
|
|
if(!MLPContinueTrainingX_lbl3(s,subset,subsetsize,ngradbatch,session,decay))
|
|
return(false);
|
|
}
|
|
else
|
|
{
|
|
//--- Routine body
|
|
//--- Check correctness of inputs
|
|
if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": internal error - parameter S is not initialized or is spoiled(S.NPoints<0)."))
|
|
return(false);
|
|
if(s.m_rcpar)
|
|
ttype=0;
|
|
else
|
|
ttype=1;
|
|
if(!CMLPBase::MLPIsSoftMax(session.m_network))
|
|
ntype=0;
|
|
else
|
|
ntype=1;
|
|
//--- check
|
|
if(!CAp::Assert(ntype==ttype,__FUNCTION__+": internal error - type of the resulting network is not similar to network type in trainer object."))
|
|
return(false);
|
|
CMLPBase::MLPProperties(session.m_network,nin,nout,wcount);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": internal error - number of inputs in trainer is not equal to number of inputs in the network."))
|
|
return(false);
|
|
if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": internal error - number of outputs in trainer is not equal to number of outputs in the network."))
|
|
return(false);
|
|
if(!CAp::Assert(subset.Size()>=subsetsize,__FUNCTION__+": internal error - parameter SubsetSize more than input subset size(Length(Subset)<SubsetSize)."))
|
|
return(false);
|
|
if(!CAp::Assert(subsetsize<=0 || (subset.Min()>=0 && subset.Max()<s.m_npoints),__FUNCTION__+": internal error - parameter Subset contains incorrect index(Subset[I]<0 or Subset[I]>S.NPoints-1)."))
|
|
return(false);
|
|
//--- Quick exit on empty training set
|
|
if(s.m_npoints==0 || subsetsize==0)
|
|
return(false);
|
|
//--- Minibatch training
|
|
if(!CAp::Assert(!(session.m_algoused==1),"MINIBATCH TRAINING IS NOT IMPLEMENTED YET"))
|
|
return(false);
|
|
//--- Last option: full batch training
|
|
decay=s.m_decay;
|
|
//---
|
|
if(!MLPContinueTrainingX_lbl1(s,subset,subsetsize,ngradbatch,session,decay))
|
|
return(false);
|
|
}
|
|
//--- Saving state
|
|
session.m_rstate.ia.Set(0,nin);
|
|
session.m_rstate.ia.Set(1,nout);
|
|
session.m_rstate.ia.Set(2,wcount);
|
|
session.m_rstate.ia.Set(3,twcount);
|
|
session.m_rstate.ia.Set(4,ntype);
|
|
session.m_rstate.ia.Set(5,ttype);
|
|
session.m_rstate.ia.Set(6,i);
|
|
session.m_rstate.ia.Set(7,j);
|
|
session.m_rstate.ia.Set(8,k);
|
|
session.m_rstate.ia.Set(9,trnsetsize);
|
|
session.m_rstate.ia.Set(10,epoch);
|
|
session.m_rstate.ia.Set(11,minibatchcount);
|
|
session.m_rstate.ia.Set(12,minibatchidx);
|
|
session.m_rstate.ia.Set(13,cursize);
|
|
session.m_rstate.ia.Set(14,idx0);
|
|
session.m_rstate.ia.Set(15,idx1);
|
|
session.m_rstate.ra.Set(0,decay);
|
|
session.m_rstate.ra.Set(1,v);
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CMLPTrain::MLPContinueTrainingX_lbl1(CMLPTrainer &s,CRowInt &subset,
|
|
int subsetsize,
|
|
int &ngradbatch,
|
|
CSMLPTrnSession &session,double &decay)
|
|
{
|
|
if(!CMinLBFGS::MinLBFGSIteration(session.m_optimizer))
|
|
{
|
|
CMinLBFGS::MinLBFGSResultsBuf(session.m_optimizer,session.m_network.m_weights,session.m_optimizerrep);
|
|
return(false);
|
|
}
|
|
if(!session.m_optimizer.m_xupdated)
|
|
{
|
|
if(!MLPContinueTrainingX_lbl3(s,subset,subsetsize,ngradbatch,session,decay))
|
|
return(false);
|
|
}
|
|
session.m_network.m_weights=session.m_optimizer.m_x;
|
|
session.m_rstate.stage=0;
|
|
//--- return result
|
|
return(true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CMLPTrain::MLPContinueTrainingX_lbl3(CMLPTrainer &s,CRowInt &subset,
|
|
int subsetsize,
|
|
int &ngradbatch,
|
|
CSMLPTrnSession &session,double &decay)
|
|
{
|
|
session.m_network.m_weights=session.m_optimizer.m_x;
|
|
switch(s.m_datatype)
|
|
{
|
|
case 0:
|
|
CMLPBase::MLPGradBatchSubset(session.m_network,s.m_densexy,s.m_npoints,subset,subsetsize,session.m_optimizer.m_f,session.m_optimizer.m_g);
|
|
break;
|
|
case 1:
|
|
CMLPBase::MLPGradBatchSparseSubset(session.m_network,s.m_sparsexy,s.m_npoints,subset,subsetsize,session.m_optimizer.m_f,session.m_optimizer.m_g);
|
|
break;
|
|
}
|
|
//--- Increment number of operations performed on batch gradient
|
|
ngradbatch++;
|
|
double v=session.m_network.m_weights.Dot(session.m_network.m_weights);
|
|
session.m_optimizer.m_f+=0.5*decay*v;
|
|
session.m_optimizer.m_g+=session.m_network.m_weights*decay+0;
|
|
//--- return result
|
|
return(MLPContinueTrainingX_lbl1(s,subset,subsetsize,ngradbatch,session,decay));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Internal bagging subroutine. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPEBaggingInternal(CMLPEnsemble &ensemble,
|
|
CMatrixDouble &xy,
|
|
int npoints,
|
|
double decay,
|
|
int restarts,
|
|
double wstep,
|
|
int maxits,
|
|
bool lmalgorithm,
|
|
int &info,
|
|
CMLPReport &rep,
|
|
CMLPCVReport &ooberrors)
|
|
{
|
|
//--- create variables
|
|
CMatrixDouble xys;
|
|
CMatrixDouble oobbuf;
|
|
CRowInt oobcntbuf;
|
|
CRowDouble x;
|
|
CRowDouble y;
|
|
CRowDouble dy;
|
|
CRowDouble dsbuf;
|
|
bool s[];
|
|
int ccnt=0;
|
|
int pcnt=0;
|
|
int j=0;
|
|
double v=0;
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int i1_=0;
|
|
CMLPReport tmprep;
|
|
CHighQualityRandState rs;
|
|
|
|
info=0;
|
|
CMLPBase::MLPProperties(ensemble.m_network,nin,nout,wcount);
|
|
//--- Test for inputs
|
|
if(!lmalgorithm && wstep==0.0 && maxits==0)
|
|
{
|
|
info=-8;
|
|
return;
|
|
}
|
|
if(npoints<=0 || restarts<1 || wstep<0.0 || maxits<0)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
{
|
|
vector<double> temp=xy.Col(nin);
|
|
if((int)MathRound(temp.Min())<0 || (int)MathRound(temp.Max())>=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;
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
{
|
|
ccnt=nin+1;
|
|
pcnt=nin;
|
|
}
|
|
else
|
|
{
|
|
ccnt=nin+nout;
|
|
pcnt=nin+nout;
|
|
}
|
|
xys.Resize(npoints,ccnt);
|
|
ArrayResize(s,npoints);
|
|
oobbuf=matrix<double>::Zeros(npoints,nout);
|
|
oobcntbuf.Resize(npoints);
|
|
oobcntbuf.Fill(0);
|
|
x.Resize(nin);
|
|
y.Resize(nout);
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
dy.Resize(1);
|
|
else
|
|
dy.Resize(nout);
|
|
//--- main bagging cycle
|
|
CHighQualityRand::HQRndRandomize(rs);
|
|
for(int k=0; k<ensemble.m_ensemblesize; k++)
|
|
{
|
|
//--- prepare dataset
|
|
ArrayInitialize(s,false);
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
j=CHighQualityRand::HQRndUniformI(rs,npoints);
|
|
s[j]=true;
|
|
xys.Row(i,xy[j]+0);
|
|
}
|
|
//--- train
|
|
if(lmalgorithm)
|
|
MLPTrainLM(ensemble.m_network,xys,npoints,decay,restarts,info,tmprep);
|
|
else
|
|
MLPTrainLBFGS(ensemble.m_network,xys,npoints,decay,restarts,wstep,maxits,info,tmprep);
|
|
if(info<0)
|
|
return;
|
|
//--- save results
|
|
rep.m_ngrad+=tmprep.m_ngrad;
|
|
rep.m_nhess+=tmprep.m_nhess;
|
|
rep.m_ncholesky+=tmprep.m_ncholesky;
|
|
i1_= -(k*wcount);
|
|
for(int i_=k*wcount; i_<(k+1)*wcount; i_++)
|
|
ensemble.m_weights.Set(i_,ensemble.m_network.m_weights[i_+i1_]);
|
|
i1_=-(k*pcnt);
|
|
for(int i_=k*pcnt; i_<(k+1)*pcnt; i_++)
|
|
{
|
|
ensemble.m_columnmeans.Set(i_,ensemble.m_network.m_columnmeans[i_+i1_]);
|
|
ensemble.m_columnsigmas.Set(i_,ensemble.m_network.m_columnsigmas[i_+i1_]);
|
|
}
|
|
//--- OOB estimates
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
if(!s[i])
|
|
{
|
|
x=xy[i]+0;
|
|
CMLPBase::MLPProcess(ensemble.m_network,x,y);
|
|
oobbuf.Row(i,y.ToVector()+oobbuf[i]);
|
|
oobcntbuf.Add(i,1);
|
|
}
|
|
}
|
|
}
|
|
//--- OOB estimates
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
CBdSS::DSErrAllocate(nout,dsbuf);
|
|
else
|
|
CBdSS::DSErrAllocate(-nout,dsbuf);
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
if(oobcntbuf[i]!=0)
|
|
{
|
|
v=1.0/(double)oobcntbuf[i];
|
|
y=oobbuf[i]*v;
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
dy.Set(0,xy.Get(i,nin));
|
|
else
|
|
{
|
|
i1_=(nin);
|
|
for(int i_=0; i_<nout; i_++)
|
|
dy.Set(i_,v*xy.Get(i,i_+i1_));
|
|
}
|
|
CBdSS::DSErrAccumulate(dsbuf,y,dy);
|
|
}
|
|
}
|
|
CBdSS::DSErrFinish(dsbuf);
|
|
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];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes temporaries needed for training |
|
|
//| session. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::InitMLPTrnSession(CMultilayerPerceptron &networktrained,
|
|
bool randomizenetwork,
|
|
CMLPTrainer &trainer,
|
|
CSMLPTrnSession &session)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int pcount=0;
|
|
CRowInt dummysubset;
|
|
//--- Prepare network:
|
|
//--- * copy input network to Session.Network
|
|
//--- * re-initialize preprocessor and weights if RandomizeNetwork=True
|
|
CMLPBase::MLPCopy(networktrained,session.m_network);
|
|
if(randomizenetwork)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(trainer.m_datatype==0 || trainer.m_datatype==1,__FUNCTION__+": unexpected Trainer.DataType"))
|
|
return;
|
|
switch(trainer.m_datatype)
|
|
{
|
|
case 0:
|
|
CMLPBase::MLPInitPreprocessorSubset(session.m_network,trainer.m_densexy,trainer.m_npoints,dummysubset,-1);
|
|
break;
|
|
case 1:
|
|
CMLPBase::MLPInitPreprocessorSparseSubset(session.m_network,trainer.m_sparsexy,trainer.m_npoints,dummysubset,-1);
|
|
break;
|
|
}
|
|
CMLPBase::MLPRandomize(session.m_network);
|
|
session.m_randomizenetwork=true;
|
|
}
|
|
else
|
|
session.m_randomizenetwork=false;
|
|
//--- Determine network geometry and initialize optimizer
|
|
CMLPBase::MLPProperties(session.m_network,nin,nout,wcount);
|
|
CMinLBFGS::MinLBFGSCreate(wcount,MathMin(wcount,trainer.m_lbfgsfactor),session.m_network.m_weights,session.m_optimizer);
|
|
CMinLBFGS::MinLBFGSSetXRep(session.m_optimizer,true);
|
|
//--- Create buffers
|
|
session.m_wbuf0.Resize(wcount);
|
|
session.m_wbuf1.Resize(wcount);
|
|
//--- Initialize session result
|
|
CMLPBase::MLPExportTunableParameters(session.m_network,session.m_bestparameters,pcount);
|
|
session.m_bestrmserror=CMath::m_maxrealnumber;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Neural networks ensemble |
|
|
//+------------------------------------------------------------------+
|
|
class CMLPE
|
|
{
|
|
public:
|
|
//--- class constants
|
|
static const int m_mlpefirstversion;
|
|
//--- 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 MLPEAlloc(CSerializer &s,CMLPEnsemble &ensemble);
|
|
static void MLPESerialize(CSerializer &s,CMLPEnsemble &ensemble);
|
|
static void MLPEUnserialize(CSerializer &s,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 MLPEProcess(CMLPEnsemble &ensemble,CRowDouble &x,CRowDouble &y);
|
|
static void MLPEProcessI(CMLPEnsemble &ensemble,double &x[],double &y[]);
|
|
static void MLPEProcessI(CMLPEnsemble &ensemble,CRowDouble &x,CRowDouble &y);
|
|
static void MLPEAllErrorsX(CMLPEnsemble &ensemble,CMatrixDouble &densexy,CSparseMatrix &sparsexy,int datasetsize,int datasettype,CRowInt &idx,int subset0,int subset1,int subsettype,CModelErrors &rep);
|
|
static void MLPEAllerrorsSparse(CMLPEnsemble &ensemble,CSparseMatrix &xy,int npoints,double &relcls,double &avgce,double &rms,double &avg,double &avgrel);
|
|
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);
|
|
|
|
private:
|
|
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);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize constants |
|
|
//+------------------------------------------------------------------+
|
|
const int CMLPE::m_mlpefirstversion=1;
|
|
//+------------------------------------------------------------------+
|
|
//| Like MLPCreate0, but for ensembles. |
|
|
//+------------------------------------------------------------------+
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPECreateFromNetwork(CMultilayerPerceptron &network,
|
|
const int ensemblesize,
|
|
CMLPEnsemble &ensemble)
|
|
{
|
|
//--- create variables
|
|
int ccount=0;
|
|
int i1_=0;
|
|
//--- check
|
|
if(!CAp::Assert(ensemblesize>0,__FUNCTION__+": incorrect ensemble size!"))
|
|
return;
|
|
//--- Copy network
|
|
CMLPBase::MLPCopy(network,ensemble.m_network);
|
|
//--- network properties
|
|
if(CMLPBase::MLPIsSoftMax(network))
|
|
ccount=CMLPBase::MLPGetInputsCount(ensemble.m_network);
|
|
else
|
|
ccount=CMLPBase::MLPGetInputsCount(ensemble.m_network)+CMLPBase::MLPGetOutputsCount(ensemble.m_network);
|
|
int wcount=CMLPBase::MLPGetWeightsCount(ensemble.m_network);
|
|
ensemble.m_ensemblesize=ensemblesize;
|
|
//--- weights, means, sigmas
|
|
ensemble.m_weights=vector<double>::Zeros(ensemblesize*wcount);
|
|
ensemble.m_columnmeans=vector<double>::Zeros(ensemblesize*ccount);
|
|
ensemble.m_columnsigmas=vector<double>::Zeros(ensemblesize*ccount);
|
|
for(int i=0; i<ensemblesize*wcount; i++)
|
|
ensemble.m_weights.Set(i,CMath::RandomReal()-0.5);
|
|
for(int i=0; i<ensemblesize; i++)
|
|
{
|
|
i1_=-(i*ccount);
|
|
for(int i_=i*ccount; i_<(i+1)*ccount; i_++)
|
|
{
|
|
ensemble.m_columnmeans.Set(i_,network.m_columnmeans[i_+i1_]);
|
|
ensemble.m_columnsigmas.Set(i_,network.m_columnsigmas[i_+i1_]);
|
|
}
|
|
}
|
|
//--- temporaries, internal buffers
|
|
ensemble.m_y.Resize(CMLPBase::MLPGetOutputsCount(ensemble.m_network));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Copying of MLPEnsemble strucure |
|
|
//| INPUT PARAMETERS: |
|
|
//| Ensemble1 - original |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Ensemble2 - copy |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPECopy(CMLPEnsemble &ensemble1,CMLPEnsemble &ensemble2)
|
|
{
|
|
ensemble2=ensemble1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: allocation |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPEAlloc(CSerializer &s,CMLPEnsemble &ensemble)
|
|
{
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
CApServ::AllocRealArray(s,ensemble.m_weights,-1);
|
|
CApServ::AllocRealArray(s,ensemble.m_columnmeans,-1);
|
|
CApServ::AllocRealArray(s,ensemble.m_columnsigmas,-1);
|
|
CMLPBase::MLPAlloc(s,ensemble.m_network);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: serialization |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPESerialize(CSerializer &s,CMLPEnsemble &ensemble)
|
|
{
|
|
s.Serialize_Int(CSCodes::GetMLPESerializationCode());
|
|
s.Serialize_Int(m_mlpefirstversion);
|
|
s.Serialize_Int(ensemble.m_ensemblesize);
|
|
CApServ::SerializeRealArray(s,ensemble.m_weights,-1);
|
|
CApServ::SerializeRealArray(s,ensemble.m_columnmeans,-1);
|
|
CApServ::SerializeRealArray(s,ensemble.m_columnsigmas,-1);
|
|
CMLPBase::MLPSerialize(s,ensemble.m_network);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: unserialization |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPEUnserialize(CSerializer &s,CMLPEnsemble &ensemble)
|
|
{
|
|
//--- check correctness of header
|
|
int i0=s.Unserialize_Int();
|
|
if(!CAp::Assert(i0==CSCodes::GetMLPESerializationCode(),__FUNCTION__": stream header corrupted"))
|
|
return;
|
|
int i1=s.Unserialize_Int();
|
|
if(!CAp::Assert(i1==m_mlpefirstversion,__FUNCTION__": stream header corrupted"))
|
|
return;
|
|
//--- Create network
|
|
ensemble.m_ensemblesize=s.Unserialize_Int();
|
|
CApServ::UnserializeRealArray(s,ensemble.m_weights);
|
|
CApServ::UnserializeRealArray(s,ensemble.m_columnmeans);
|
|
CApServ::UnserializeRealArray(s,ensemble.m_columnsigmas);
|
|
CMLPBase::MLPUnserialize(s,ensemble.m_network);
|
|
//--- Allocate termoraries
|
|
ensemble.m_y.Resize(CMLPBase::MLPGetOutputsCount(ensemble.m_network));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Randomization of MLP ensemble |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPERandomize(CMLPEnsemble &ensemble)
|
|
{
|
|
//--- calculation
|
|
int wcount=CMLPBase::MLPGetWeightsCount(ensemble.m_network);
|
|
for(int i=0; i<=ensemble.m_ensemblesize*wcount-1; i++)
|
|
ensemble.m_weights.Set(i,CMath::RandomReal()-0.5);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return ensemble properties (number of inputs and outputs). |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPEProperties(CMLPEnsemble &ensemble,int &nin,int &nout)
|
|
{
|
|
//--- change values
|
|
nin=CMLPBase::MLPGetInputsCount(ensemble.m_network);
|
|
nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return normalization type (whether ensemble is SOFTMAX-normalized|
|
|
//| or not). |
|
|
//+------------------------------------------------------------------+
|
|
bool CMLPE::MLPEIsSoftMax(CMLPEnsemble &ensemble)
|
|
{
|
|
return(CMLPBase::MLPIsSoftMax(ensemble.m_network));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPEProcess(CMLPEnsemble &ensemble,double &x[],double &y[])
|
|
{
|
|
CRowDouble X=x;
|
|
CRowDouble Y;
|
|
MLPEProcess(ensemble,X,Y);
|
|
Y.ToArray(y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPEProcess(CMLPEnsemble &ensemble,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
//--- create variables
|
|
int es=0;
|
|
int wc=0;
|
|
int cc=0;
|
|
double v=0;
|
|
int nout=0;
|
|
int i1_=0;
|
|
//--- initialization
|
|
es=ensemble.m_ensemblesize;
|
|
wc=CMLPBase::MLPGetWeightsCount(ensemble.m_network);
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
cc=CMLPBase::MLPGetInputsCount(ensemble.m_network);
|
|
else
|
|
cc=CMLPBase::MLPGetInputsCount(ensemble.m_network)+CMLPBase::MLPGetOutputsCount(ensemble.m_network);
|
|
//--- calculate
|
|
v=1.0/(double)es;
|
|
nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
|
|
y=vector<double>::Zeros(nout);
|
|
for(int i=0; i<es; i++)
|
|
{
|
|
i1_=(i*wc);
|
|
for(int i_=0; i_<wc; i_++)
|
|
ensemble.m_network.m_weights.Set(i_,ensemble.m_weights[i_+i1_]);
|
|
i1_=(i*cc);
|
|
for(int i_=0; i_<cc; i_++)
|
|
ensemble.m_network.m_columnmeans.Set(i_,ensemble.m_columnmeans[i_+i1_]);
|
|
i1_=(i*cc);
|
|
for(int i_=0; i_<cc; i_++)
|
|
ensemble.m_network.m_columnsigmas.Set(i_,ensemble.m_columnsigmas[i_+i1_]);
|
|
CMLPBase::MLPProcess(ensemble.m_network,x,ensemble.m_y);
|
|
for(int i_=0; i_<nout; i_++)
|
|
y.Add(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. |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPEProcessI(CMLPEnsemble &ensemble,double &x[],double &y[])
|
|
{
|
|
MLPEProcess(ensemble,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPEProcessI(CMLPEnsemble &ensemble,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
MLPEProcess(ensemble,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Calculation of all types of errors |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPEAllErrorsX(CMLPEnsemble &ensemble,
|
|
CMatrixDouble &densexy,
|
|
CSparseMatrix &sparsexy,
|
|
int datasetsize,
|
|
int datasettype,
|
|
CRowInt &idx,
|
|
int subset0,
|
|
int subset1,
|
|
int subsettype,
|
|
CModelErrors &rep)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
bool iscls;
|
|
int srcidx=0;
|
|
int i1_=0;
|
|
CMLPBuffers pbuf;
|
|
CModelErrors rep0;
|
|
CModelErrors rep1;
|
|
//--- Get network information
|
|
nin=CMLPBase::MLPGetInputsCount(ensemble.m_network);
|
|
nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
|
|
iscls=CMLPBase::MLPIsSoftMax(ensemble.m_network);
|
|
//--- Retrieve buffer, prepare, process data, recycle buffer
|
|
if(iscls)
|
|
CBdSS::DSErrAllocate(nout,pbuf.m_Tmp0);
|
|
else
|
|
CBdSS::DSErrAllocate(-nout,pbuf.m_Tmp0);
|
|
CApServ::RVectorSetLengthAtLeast(pbuf.m_X,nin);
|
|
CApServ::RVectorSetLengthAtLeast(pbuf.m_Y,nout);
|
|
CApServ::RVectorSetLengthAtLeast(pbuf.m_Desiredy,nout);
|
|
for(int i=subset0; i<subset1; i++)
|
|
{
|
|
srcidx=-1;
|
|
if(subsettype==0)
|
|
srcidx=i;
|
|
if(subsettype==1)
|
|
srcidx=idx[i];
|
|
if(!CAp::Assert(srcidx>=0,__FUNCTION__": internal error"))
|
|
return;
|
|
switch(datasettype)
|
|
{
|
|
case 0:
|
|
pbuf.m_X=densexy[srcidx]+0;
|
|
break;
|
|
case 1:
|
|
CSparse::SparseGetRow(sparsexy,srcidx,pbuf.m_X);
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__": wrong Datase Type");
|
|
return;
|
|
}
|
|
pbuf.m_X.Resize(nin);
|
|
MLPEProcess(ensemble,pbuf.m_X,pbuf.m_Y);
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
{
|
|
if(datasettype==0)
|
|
pbuf.m_Desiredy.Set(0,densexy.Get(srcidx,nin));
|
|
else
|
|
pbuf.m_Desiredy.Set(0,CSparse::SparseGet(sparsexy,srcidx,nin));
|
|
}
|
|
else
|
|
{
|
|
if(datasettype==0)
|
|
{
|
|
i1_=nin;
|
|
for(int i_=0; i_<nout; i_++)
|
|
pbuf.m_Desiredy.Set(i_,densexy.Get(srcidx,i_+i1_));
|
|
}
|
|
else
|
|
{
|
|
for(int j=0; j<nout; j++)
|
|
pbuf.m_Desiredy.Set(j,CSparse::SparseGet(sparsexy,srcidx,nin+j));
|
|
}
|
|
}
|
|
CBdSS::DSErrAccumulate(pbuf.m_Tmp0,pbuf.m_Y,pbuf.m_Desiredy);
|
|
}
|
|
CBdSS::DSErrFinish(pbuf.m_Tmp0);
|
|
rep.m_RelCLSError=pbuf.m_Tmp0[0];
|
|
rep.m_AvgCE=pbuf.m_Tmp0[1]/MathLog(2);
|
|
rep.m_RMSError=pbuf.m_Tmp0[2];
|
|
rep.m_AvgError=pbuf.m_Tmp0[3];
|
|
rep.m_AvgRelError=pbuf.m_Tmp0[4];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Calculation of all types of errors on dataset given by sparse |
|
|
//| matrix |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPEAllerrorsSparse(CMLPEnsemble &ensemble,
|
|
CSparseMatrix &xy,
|
|
int npoints,double &relcls,
|
|
double &avgce,double &rms,
|
|
double &avg,double &avgrel)
|
|
{
|
|
//--- create variables
|
|
int nin=CMLPBase::MLPGetInputsCount(ensemble.m_network);
|
|
int nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
|
|
int i1_=0;
|
|
CRowDouble buf;
|
|
CRowDouble workx;
|
|
CRowDouble y;
|
|
CRowDouble dy;
|
|
|
|
relcls=0;
|
|
avgce=0;
|
|
rms=0;
|
|
avg=0;
|
|
avgrel=0;
|
|
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
{
|
|
dy.Resize(1);
|
|
CBdSS::DSErrAllocate(nout,buf);
|
|
}
|
|
else
|
|
{
|
|
dy.Resize(nout);
|
|
CBdSS::DSErrAllocate(-nout,buf);
|
|
}
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
CSparse::SparseGetRow(xy,i,workx);
|
|
MLPEProcess(ensemble,workx,y);
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
dy.Set(0,workx[nin]);
|
|
else
|
|
{
|
|
i1_=nin;
|
|
for(int i_=0; i_<=nout-1; i_++)
|
|
dy.Set(i_,workx[i_+i1_]);
|
|
}
|
|
CBdSS::DSErrAccumulate(buf,y,dy);
|
|
}
|
|
CBdSS::DSErrFinish(buf);
|
|
relcls=buf[0];
|
|
avgce=buf[1];
|
|
rms=buf[2];
|
|
avg=buf[3];
|
|
avgrel=buf[4];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPE::MLPERelClsError(CMLPEnsemble &ensemble,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
//--- create variables
|
|
CModelErrors rep;
|
|
//--- function call
|
|
MLPEAllErrorsX(ensemble,xy,ensemble.m_network.m_dummysxy,npoints,0,ensemble.m_network.m_dummyidx,0,npoints,0,rep);
|
|
//--- return result
|
|
return(rep.m_RelCLSError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPE::MLPEAvgCE(CMLPEnsemble &ensemble,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
//--- create variables
|
|
CModelErrors rep;
|
|
//--- function call
|
|
MLPEAllErrorsX(ensemble,xy,ensemble.m_network.m_dummysxy,npoints,0,ensemble.m_network.m_dummyidx,0,npoints,0,rep);
|
|
//--- return result
|
|
return(rep.m_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. |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPE::MLPERMSError(CMLPEnsemble &ensemble,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
//--- create variables
|
|
CModelErrors rep;
|
|
//--- function call
|
|
MLPEAllErrorsX(ensemble,xy,ensemble.m_network.m_dummysxy,npoints,0,ensemble.m_network.m_dummyidx,0,npoints,0,rep);
|
|
//--- return result
|
|
return(rep.m_RMSError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPE::MLPEAvgError(CMLPEnsemble &ensemble,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
//--- create variables
|
|
CModelErrors rep;
|
|
//--- function call
|
|
MLPEAllErrorsX(ensemble,xy,ensemble.m_network.m_dummysxy,npoints,0,ensemble.m_network.m_dummyidx,0,npoints,0,rep);
|
|
//--- return result
|
|
return(rep.m_AvgError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
double CMLPE::MLPEAvgRelError(CMLPEnsemble &ensemble,CMatrixDouble &xy,
|
|
const int npoints)
|
|
{
|
|
//--- create variables
|
|
CModelErrors rep;
|
|
//--- function call
|
|
MLPEAllErrorsX(ensemble,xy,ensemble.m_network.m_dummysxy,npoints,0,ensemble.m_network.m_dummyidx,0,npoints,0,rep);
|
|
//--- return result
|
|
return(rep.m_AvgRelError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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 |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::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 |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::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 |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPETrainES(CMLPEnsemble &ensemble,CMatrixDouble &xy,
|
|
const int npoints,const double decay,
|
|
const int restarts,int &info,CMLPReport &rep)
|
|
{
|
|
//--- create variables
|
|
int ccount=0;
|
|
int pcount=0;
|
|
int trnsize=0;
|
|
int valsize=0;
|
|
int tmpinfo=0;
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
int i1_=0;
|
|
//--- create matrix
|
|
CMatrixDouble trnxy;
|
|
CMatrixDouble valxy;
|
|
//--- objects of classes
|
|
CMLPReport tmprep;
|
|
CModelErrors moderr;
|
|
//--- initialization
|
|
info=0;
|
|
nin=CMLPBase::MLPGetInputsCount(ensemble.m_network);
|
|
nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
|
|
wcount=CMLPBase::MLPGetWeightsCount(ensemble.m_network);
|
|
//--- check
|
|
if((npoints<2 || restarts<1) || decay<0.0)
|
|
{
|
|
info=-1;
|
|
return;
|
|
}
|
|
//--- check
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
{
|
|
vector<double> temp=xy.Col(nin);
|
|
temp.Resize(npoints);
|
|
//--- check
|
|
if((int)MathRound(temp.Min())<0 || (int)MathRound(temp.Max())>=nout)
|
|
{
|
|
info=-2;
|
|
return;
|
|
}
|
|
ccount=nin+1;
|
|
pcount=nin;
|
|
}
|
|
else
|
|
{
|
|
ccount=nin+nout;
|
|
pcount=nin+nout;
|
|
}
|
|
//--- change value
|
|
info=6;
|
|
//--- allocation
|
|
trnxy.Resize(npoints,ccount);
|
|
valxy.Resize(npoints,ccount);
|
|
//--- change values
|
|
rep.m_ngrad=0;
|
|
rep.m_nhess=0;
|
|
rep.m_ncholesky=0;
|
|
//--- train networks
|
|
for(int k=0; k<=ensemble.m_ensemblesize-1; k++)
|
|
{
|
|
//--- Split set
|
|
do
|
|
{
|
|
trnsize=0;
|
|
valsize=0;
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
//--- check
|
|
if(CMath::RandomReal()<0.66)
|
|
{
|
|
//--- Assign sample to training set
|
|
trnxy.Row(trnsize,xy[i]+0);
|
|
trnsize++;
|
|
}
|
|
else
|
|
{
|
|
//--- Assign sample to validation set
|
|
valxy.Row(valsize,xy[i]+0);
|
|
valsize++;
|
|
}
|
|
}
|
|
}
|
|
while(!(trnsize!=0 && valsize!=0));
|
|
//--- Train
|
|
MLPTrainES(ensemble.m_network,trnxy,trnsize,valxy,valsize,decay,restarts,tmpinfo,tmprep);
|
|
//--- check
|
|
if(tmpinfo<0)
|
|
{
|
|
info=tmpinfo;
|
|
return;
|
|
}
|
|
//--- save results
|
|
i1_=-(k*wcount);
|
|
for(int i_=k*wcount; i_<(k+1)*wcount; i_++)
|
|
ensemble.m_weights.Set(i_,ensemble.m_network.m_weights[i_+i1_]);
|
|
i1_=-(k*pcount);
|
|
for(int i_=k*pcount; i_<(k+1)*pcount; i_++)
|
|
ensemble.m_columnmeans.Set(i_,ensemble.m_network.m_columnmeans[i_+i1_]);
|
|
i1_=-(k*pcount);
|
|
for(int i_=k*pcount; i_<(k+1)*pcount; i_++)
|
|
ensemble.m_columnsigmas.Set(i_,ensemble.m_network.m_columnsigmas[i_+i1_]);
|
|
//--- change values
|
|
rep.m_ngrad+=tmprep.m_ngrad;
|
|
rep.m_nhess+=tmprep.m_nhess;
|
|
rep.m_ncholesky+=tmprep.m_ncholesky;
|
|
}
|
|
CMLPE::MLPEAllErrorsX(ensemble,xy,ensemble.m_network.m_dummysxy,npoints,0,ensemble.m_network.m_dummyidx,0,npoints,0,moderr);
|
|
rep.m_RelCLSError=moderr.m_RelCLSError;
|
|
rep.m_AvgCE=moderr.m_AvgCE;
|
|
rep.m_RMSError=moderr.m_RMSError;
|
|
rep.m_AvgError=moderr.m_AvgError;
|
|
rep.m_AvgRelError=moderr.m_AvgRelError;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function trains neural network ensemble passed to this |
|
|
//| function using current dataset and early stopping training |
|
|
//| algorithm. Each early stopping round performs NRestarts random |
|
|
//| restarts (thus, EnsembleSize*NRestarts training rounds is |
|
|
//| performed in total). |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - trainer object; |
|
|
//| Ensemble - neural network ensemble. It must have same |
|
|
//| number of inputs and outputs/classes as was |
|
|
//| specified during creation of the trainer object.|
|
|
//| NRestarts - number of restarts, >=0: |
|
|
//| * NRestarts>0 means that specified number of |
|
|
//| random restarts are performed during each ES |
|
|
//| round; |
|
|
//| * NRestarts=0 is silently replaced by 1. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Ensemble - trained ensemble; |
|
|
//| Rep - it contains all type of errors. |
|
|
//| NOTE: this training method uses BOTH early stopping and weight |
|
|
//| decay! So, you should select weight decay before starting |
|
|
//| training just as you select it before training |
|
|
//| "conventional" networks. |
|
|
//| NOTE: when no dataset was specified with MLPSetDataset / |
|
|
//| SetSparseDataset(), or single-point dataset was passed, |
|
|
//| ensemble is filled by zero values. |
|
|
//| NOTE: this method uses sum-of-squares error function for training|
|
|
//+------------------------------------------------------------------+
|
|
void CMLPTrain::MLPTrainEnsembleES(CMLPTrainer &s,
|
|
CMLPEnsemble &ensemble,
|
|
int nrestarts,
|
|
CMLPReport &rep)
|
|
{
|
|
//--- create variables
|
|
int nin=0;
|
|
int nout=0;
|
|
int ntype=0;
|
|
int ttype=0;
|
|
int sgrad=0;
|
|
CModelErrors tmprep;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_npoints>=0,__FUNCTION__+": parameter S is not initialized or is spoiled(S.NPoints<0)"))
|
|
return;
|
|
if(!CMLPE::MLPEIsSoftMax(ensemble))
|
|
ntype=0;
|
|
else
|
|
ntype=1;
|
|
if(s.m_rcpar)
|
|
ttype=0;
|
|
else
|
|
ttype=1;
|
|
//--- check
|
|
if(!CAp::Assert(ntype==ttype,__FUNCTION__+": internal error - type of input network is not similar to network type in trainer object"))
|
|
return;
|
|
nin=CMLPBase::MLPGetInputsCount(ensemble.m_network);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nin==nin,__FUNCTION__+": number of inputs in trainer is not equal to number of inputs in ensemble network"))
|
|
return;
|
|
|
|
nout=CMLPBase::MLPGetOutputsCount(ensemble.m_network);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_nout==nout,__FUNCTION__+": number of outputs in trainer is not equal to number of outputs in ensemble network"))
|
|
return;
|
|
if(!CAp::Assert(nrestarts>=0,__FUNCTION__+": NRestarts<0."))
|
|
return;
|
|
//--- Initialize parameter Rep
|
|
rep.m_RelCLSError=0;
|
|
rep.m_AvgCE=0;
|
|
rep.m_RMSError=0;
|
|
rep.m_AvgError=0;
|
|
rep.m_AvgRelError=0;
|
|
rep.m_ngrad=0;
|
|
rep.m_nhess=0;
|
|
rep.m_ncholesky=0;
|
|
//--- Allocate
|
|
s.m_subset.Resize(s.m_npoints);
|
|
s.m_valsubset.Resize(s.m_npoints);
|
|
//--- Start training
|
|
//--- NOTE: ESessions is not initialized because MLPTrainEnsembleX
|
|
//--- needs uninitialized pool.
|
|
sgrad=0;
|
|
MLPTrainEnsembleX(s,ensemble,0,ensemble.m_ensemblesize,nrestarts,0,sgrad);
|
|
rep.m_ngrad=sgrad;
|
|
//--- Calculate errors.
|
|
CMLPE::MLPEAllErrorsX(ensemble,s.m_densexy,s.m_sparsexy,s.m_npoints,s.m_datatype,ensemble.m_network.m_dummyidx,0,s.m_npoints,0,tmprep);
|
|
rep.m_RelCLSError=tmprep.m_RelCLSError;
|
|
rep.m_AvgCE=tmprep.m_AvgCE;
|
|
rep.m_RMSError=tmprep.m_RMSError;
|
|
rep.m_AvgError=tmprep.m_AvgError;
|
|
rep.m_AvgRelError=tmprep.m_AvgRelError;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Calculation of all types of errors |
|
|
//+------------------------------------------------------------------+
|
|
void CMLPE::MLPEAllErrors(CMLPEnsemble &ensemble,CMatrixDouble &xy,
|
|
const int npoints,double &relcls,
|
|
double &avgce,double &rms,
|
|
double &avg,double &avgrel)
|
|
{
|
|
//--- create variables
|
|
int i1_=0;
|
|
int nin=0;
|
|
int nout=0;
|
|
int wcount=0;
|
|
//--- creating arrays
|
|
CRowDouble buf;
|
|
CRowDouble workx;
|
|
CRowDouble y;
|
|
CRowDouble dy;
|
|
//--- initialization
|
|
relcls=0;
|
|
avgce=0;
|
|
rms=0;
|
|
avg=0;
|
|
avgrel=0;
|
|
//--- allocation
|
|
CMLPBase::MLPProperties(ensemble.m_network,nin,nout,wcount);
|
|
workx.Resize(nin);
|
|
y.Resize(nout);
|
|
//--- check
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
{
|
|
//--- allocation
|
|
dy.Resize(1);
|
|
//--- function call
|
|
CBdSS::DSErrAllocate(nout,buf);
|
|
}
|
|
else
|
|
{
|
|
//--- allocation
|
|
dy.Resize(nout);
|
|
//--- function call
|
|
CBdSS::DSErrAllocate(-nout,buf);
|
|
}
|
|
//--- calculation
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
workx=xy[i]+0;
|
|
//--- function call
|
|
MLPEProcess(ensemble,workx,y);
|
|
//--- check
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
dy.Set(0,xy.Get(i,nin));
|
|
else
|
|
{
|
|
i1_=nin;
|
|
for(int i_=0; i_<nout; i_++)
|
|
dy.Set(i_,xy.Get(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. |
|
|
//+------------------------------------------------------------------+
|
|
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 wcount=0;
|
|
int ccnt=0;
|
|
int pcnt=0;
|
|
double v=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(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
{
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
//--- check
|
|
if((int)MathRound(xy.Get(i,nin))<0 || (int)MathRound(xy.Get(i,nin))>=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;
|
|
CMLPBase::MLPProperties(ensemble.m_network,nin,nout,wcount);
|
|
//--- check
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
{
|
|
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);
|
|
ArrayResize(x,nin);
|
|
ArrayResize(y,nout);
|
|
//--- check
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
ArrayResize(dy,1);
|
|
else
|
|
ArrayResize(dy,nout);
|
|
//--- initialization
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
for(int j=0; j<=nout-1; j++)
|
|
oobbuf.Set(i,j,0);
|
|
}
|
|
for(int i=0; i<npoints; i++)
|
|
oobcntbuf[i]=0;
|
|
//--- main bagging cycle
|
|
for(int k=0; k<=ensemble.m_ensemblesize-1; k++)
|
|
{
|
|
//--- prepare dataset
|
|
for(int i=0; i<npoints; i++)
|
|
s[i]=false;
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
int j=CMath::RandomInteger(npoints);
|
|
s[j]=true;
|
|
for(int i_=0; i_<=ccnt-1; i_++)
|
|
xys.Set(i,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*wcount);
|
|
for(int i_=k*wcount; i_<=(k+1)*wcount-1; i_++)
|
|
ensemble.m_weights.Set(i_,network.m_weights[i_+i1_]);
|
|
//--- copy
|
|
i1_=-(k*pcnt);
|
|
for(int i_=k*pcnt; i_<=(k+1)*pcnt-1; i_++)
|
|
ensemble.m_columnmeans.Set(i_,network.m_columnmeans[i_+i1_]);
|
|
//--- copy
|
|
i1_=-(k*pcnt);
|
|
for(int i_=k*pcnt; i_<=(k+1)*pcnt-1; i_++)
|
|
ensemble.m_columnsigmas.Set(i_,network.m_columnsigmas[i_+i1_]);
|
|
//--- OOB estimates
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
//--- check
|
|
if(!s[i])
|
|
{
|
|
for(int i_=0; i_<=nin-1; i_++)
|
|
x[i_]=xy.Get(i,i_);
|
|
//--- function call
|
|
CMLPBase::MLPProcess(network,x,y);
|
|
//--- change value
|
|
for(int i_=0; i_<=nout-1; i_++)
|
|
oobbuf.Set(i,i_,oobbuf[i][i_]+y[i_]);
|
|
oobcntbuf[i]=oobcntbuf[i]+1;
|
|
}
|
|
}
|
|
}
|
|
//--- OOB estimates
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
{
|
|
//--- function call
|
|
CBdSS::DSErrAllocate(nout,dsbuf);
|
|
}
|
|
else
|
|
{
|
|
//--- function call
|
|
CBdSS::DSErrAllocate(-nout,dsbuf);
|
|
}
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
//--- check
|
|
if(oobcntbuf[i]!=0)
|
|
{
|
|
v=1.0/(double)oobcntbuf[i];
|
|
for(int i_=0; i_<=nout-1; i_++)
|
|
y[i_]=v*oobbuf[i][i_];
|
|
//--- check
|
|
if(CMLPBase::MLPIsSoftMax(ensemble.m_network))
|
|
dy[0]=xy[i][nin];
|
|
else
|
|
{
|
|
i1_=nin;
|
|
for(int 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:
|
|
static void PCABuildBasis(CMatrixDouble &x,const int npoints,const int nvars,int &info,double &s2[],CMatrixDouble &v);
|
|
static void PCABuildBasis(CMatrixDouble &x,const int npoints,const int nvars,int &info,CRowDouble &s2,CMatrixDouble &v);
|
|
static void PCATruncatedSubSpace(CMatrixDouble &x,int npoints,int nvars,int nneeded,double eps,int maxits,CRowDouble &s2,CMatrixDouble &v);
|
|
static void PCATruncatedSubSpaceSparse(CSparseMatrix &x,int npoints,int nvars,int nneeded,double eps,int maxits,CRowDouble &s2,CMatrixDouble &v);
|
|
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| 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. |
|
|
//+------------------------------------------------------------------+
|
|
void CPCAnalysis::PCABuildBasis(CMatrixDouble &x,const int npoints,
|
|
const int nvars,int &info,
|
|
double &s2[],
|
|
CMatrixDouble &v)
|
|
{
|
|
CRowDouble S2=s2;
|
|
PCABuildBasis(x,npoints,nvars,info,S2,v);
|
|
S2.ToArray(s2);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CPCAnalysis::PCABuildBasis(CMatrixDouble &x,const int npoints,
|
|
const int nvars,int &info,
|
|
CRowDouble &s2,
|
|
CMatrixDouble &v)
|
|
{
|
|
//--- creating arrays
|
|
vector<double> m;
|
|
//--- 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)
|
|
{
|
|
//--- initialization
|
|
s2=vector<double>::Zeros(nvars);
|
|
v=matrix<double>::Eye(nvars,nvars);
|
|
//--- exit the function
|
|
return;
|
|
}
|
|
//--- Calculate means
|
|
CMatrixDouble mx=x;
|
|
mx.Resize(npoints,nvars);
|
|
m=mx.Mean(0);
|
|
//--- Center,apply SVD,prepare output
|
|
a=matrix<double>::Zeros(MathMax(npoints,nvars),nvars);
|
|
//--- calculation
|
|
for(int i=0; i<npoints; i++)
|
|
a.Row(i,mx[i]-m);
|
|
//--- check
|
|
if(!CSingValueDecompose::RMatrixSVD(a,MathMax(npoints,nvars),nvars,0,1,2,s2,u,vt))
|
|
{
|
|
info=-4;
|
|
return;
|
|
}
|
|
//--- check
|
|
if(npoints!=1)
|
|
s2=s2.Pow(2.0)/(npoints-1);
|
|
//--- function call
|
|
v=vt.Transpose()+0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Principal components analysis |
|
|
//| This function performs truncated PCA, i.e. returns just a few |
|
|
//| most important directions. |
|
|
//| Internally it uses iterative eigensolver which is very efficient |
|
|
//| when only a minor fraction of full basis is required. Thus, if |
|
|
//| you need full basis, it is better to use pcabuildbasis() function|
|
|
//| 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 |
|
|
//| NNeeded - number of requested components, in [1,NVars] range;|
|
|
//| this function is efficient only for NNeeded<<NVars.|
|
|
//| Eps - desired precision of vectors returned; underlying |
|
|
//| solver will stop iterations as soon as absolute |
|
|
//| error in corresponding singular values reduces to |
|
|
//| roughly eps*MAX(lambda[]), with lambda[] being |
|
|
//| array of eigen values. |
|
|
//| Zero value means that algorithm performs number of |
|
|
//| iterations specified by maxits parameter, without |
|
|
//| paying attention to precision. |
|
|
//| MaxIts - number of iterations performed by subspace |
|
|
//| iteration method. Zero value means that no limit on|
|
|
//| iteration count is placed (eps-based stopping |
|
|
//| condition is used). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S2 - array[NNeeded]. Variance values corresponding to |
|
|
//| basis vectors. |
|
|
//| V - array[NVars,NNeeded] matrix, whose columns store |
|
|
//| basis vectors. |
|
|
//| NOTE: passing eps=0 and maxits=0 results in small eps being |
|
|
//| selected as stopping condition. Exact value of automatically|
|
|
//| selected eps is version-dependent. |
|
|
//+------------------------------------------------------------------+
|
|
void CPCAnalysis::PCATruncatedSubSpace(CMatrixDouble &x,
|
|
int npoints,
|
|
int nvars,
|
|
int nneeded,
|
|
double eps,
|
|
int maxits,
|
|
CRowDouble &s2,
|
|
CMatrixDouble &v)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(npoints>=0,__FUNCTION__+": npoints<0"))
|
|
return;
|
|
if(!CAp::Assert(nvars>=1,__FUNCTION__+": nvars<1"))
|
|
return;
|
|
if(!CAp::Assert(nneeded>0,__FUNCTION__+": nneeded<1"))
|
|
return;
|
|
if(!CAp::Assert(nneeded<=nvars,__FUNCTION__+": nneeded>nvars"))
|
|
return;
|
|
if(!CAp::Assert(maxits>=0,__FUNCTION__+": maxits<0"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(eps) && (double)(eps)>=0.0,__FUNCTION__+": eps<0 or is not finite"))
|
|
return;
|
|
if(!CAp::Assert((int)CAp::Rows(x)>=npoints,__FUNCTION__+": rows(x)<npoints"))
|
|
return;
|
|
if(!CAp::Assert((int)CAp::Cols(x)>=nvars || npoints==0,__FUNCTION__+": cols(x)<nvars"))
|
|
return;
|
|
//--- create variables
|
|
CMatrixDouble a,b;
|
|
vector<double> means;
|
|
int k=0;
|
|
double vv=0;
|
|
CEigSubSpaceState solver;
|
|
CEigSubSpaceReport rep;
|
|
//--- Special case: NPoints=0
|
|
if(npoints==0)
|
|
{
|
|
s2=vector<double>::Zeros(nneeded);
|
|
v=matrix<double>::Eye(nvars,nneeded);
|
|
return;
|
|
}
|
|
//--- Center matrix
|
|
a=x;
|
|
a.Resize(npoints,nvars);
|
|
means=a.Mean(0);
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(a.Row(i,x[i]-means),__FUNCTION__+":error update row " + IntegerToString(i)))
|
|
return;
|
|
}
|
|
//--- Find eigenvalues with subspace iteration solver
|
|
CEigenVDetect::EigSubSpaceCreate(nvars,nneeded,solver);
|
|
CEigenVDetect::EigSubSpaceSetCond(solver,eps,maxits);
|
|
CEigenVDetect::EigSubSpaceOOCStart(solver,0);
|
|
while(CEigenVDetect::EigSubSpaceOOCContinue(solver))
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(solver.m_RequestType==0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
k=solver.m_RequestSize;
|
|
CApServ::RMatrixSetLengthAtLeast(b,npoints,k);
|
|
CAblas::RMatrixGemm(npoints,k,nvars,1.0,a,0,0,0,solver.m_X,0,0,0,0.0,b,0,0);
|
|
CAblas::RMatrixGemm(nvars,k,npoints,1.0,a,0,0,1,b,0,0,0,0.0,solver.m_AX,0,0);
|
|
}
|
|
CEigenVDetect::EigSubSpaceOOCStop(solver,s2,v,rep);
|
|
if(npoints!=1)
|
|
s2/=(npoints-1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sparse truncated principal components analysis |
|
|
//| This function performs sparse truncated PCA, i.e. returns just a |
|
|
//| few most important principal components for a sparse input X. |
|
|
//| Internally it uses iterative eigensolver which is very efficient |
|
|
//| when only a minor fraction of full basis is required. |
|
|
//| It should be noted that, unlike LDA, PCA does not use class |
|
|
//| labels. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - sparse dataset, sparse npoints*nvars matrix. It is |
|
|
//| recommended to use CRS sparse storage format; |
|
|
//| non-CRS input will be internally converted to CRS. |
|
|
//| Matrix contains ONLY INDEPENDENT VARIABLES, and |
|
|
//| must be EXACTLY npoints*nvars. |
|
|
//| NPoints - dataset size, NPoints>=0 |
|
|
//| NVars - number of independent variables, NVars>=1 |
|
|
//| NNeeded - number of requested components, in [1,NVars] range;|
|
|
//| this function is efficient only for NNeeded<<NVars.|
|
|
//| Eps - desired precision of vectors returned; underlying |
|
|
//| solver will stop iterations as soon as absolute |
|
|
//| error in corresponding singular values reduces to |
|
|
//| roughly eps*MAX(lambda[]), with lambda[] being |
|
|
//| array of eigen values. |
|
|
//| Zero value means that algorithm performs number of |
|
|
//| iterations specified by maxits parameter, without |
|
|
//| paying attention to precision. |
|
|
//| MaxIts - number of iterations performed by subspace |
|
|
//| iteration method. Zero value means that no limit on|
|
|
//| iteration count is placed (eps-based stopping |
|
|
//| condition is used). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S2 - array[NNeeded]. Variance values corresponding to |
|
|
//| basis vectors. |
|
|
//| V - array[NVars,NNeeded] matrix, whose columns store |
|
|
//| basis vectors. |
|
|
//| NOTE: passing eps=0 and maxits=0 results in small eps being |
|
|
//| selected as a stopping condition. Exact value of |
|
|
//| automatically selected eps is version-dependent. |
|
|
//| NOTE: zero MaxIts is silently replaced by some reasonable value |
|
|
//| which prevents eternal loops (possible when inputs are |
|
|
//| degenerate and too stringent stopping criteria are |
|
|
//| specified). In current version it is 50+2*NVars. |
|
|
//+------------------------------------------------------------------+
|
|
void CPCAnalysis::PCATruncatedSubSpaceSparse(CSparseMatrix &x,
|
|
int npoints,
|
|
int nvars,
|
|
int nneeded,
|
|
double eps,
|
|
int maxits,
|
|
CRowDouble &s2,
|
|
CMatrixDouble &v)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(npoints>=0,__FUNCTION__+": npoints<0"))
|
|
return;
|
|
if(!CAp::Assert(nvars>=1,__FUNCTION__+": nvars<1"))
|
|
return;
|
|
if(!CAp::Assert(nneeded>0,__FUNCTION__+": nneeded<1"))
|
|
return;
|
|
if(!CAp::Assert(nneeded<=nvars,__FUNCTION__+": nneeded>nvars"))
|
|
return;
|
|
if(!CAp::Assert(maxits>=0,__FUNCTION__+": maxits<0"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(eps) && eps>=0.0,__FUNCTION__+": eps<0 or is not finite"))
|
|
return;
|
|
//--- create variables
|
|
double vv=0;
|
|
//--- create arrays
|
|
CSparseMatrix xcrs;
|
|
CRowDouble b1;
|
|
CRowDouble c1;
|
|
CRowDouble z1;
|
|
CRowDouble means;
|
|
CEigSubSpaceState solver;
|
|
CEigSubSpaceReport rep;
|
|
//--- Special case: NPoints=0
|
|
if(npoints==0)
|
|
{
|
|
s2=vector<double>::Zeros(nneeded);
|
|
v=matrix<double>::Identity(nvars,nneeded);
|
|
return;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(CSparse::SparseGetNRows(x)==npoints,__FUNCTION__+": rows(x)!=npoints"))
|
|
return;
|
|
if(!CAp::Assert(CSparse::SparseGetNCols(x)==nvars,__FUNCTION__+": cols(x)!=nvars"))
|
|
return;
|
|
//--- If input data are not in CRS format, perform conversion to CRS
|
|
if(!CSparse::SparseIsCRS(x))
|
|
{
|
|
CSparse::SparseCopyToCRS(x,xcrs);
|
|
PCATruncatedSubSpaceSparse(xcrs,npoints,nvars,nneeded,eps,maxits,s2,v);
|
|
return;
|
|
}
|
|
//--- Initialize parameters, prepare buffers
|
|
if(eps==0.0 && maxits==0)
|
|
eps=1.0E-6;
|
|
if(maxits==0)
|
|
maxits=50+2*nvars;
|
|
//--- Calculate mean values
|
|
vv=1.0/(double)npoints;
|
|
b1=vector<double>::Full(npoints,vv);
|
|
CSparse::SparseMTV(x,b1,means);
|
|
//--- Find eigenvalues with subspace iteration solver
|
|
CEigenVDetect::EigSubSpaceCreate(nvars,nneeded,solver);
|
|
CEigenVDetect::EigSubSpaceSetCond(solver,eps,maxits);
|
|
CEigenVDetect::EigSubSpaceOOCStart(solver,0);
|
|
while(CEigenVDetect::EigSubSpaceOOCContinue(solver))
|
|
{
|
|
if(!CAp::Assert(solver.m_RequestType==0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
for(int k=0; k<solver.m_RequestSize; k++)
|
|
{
|
|
//--- Calculate B1=(X-meansX)*Zk
|
|
z1=solver.m_X.Col(k)+0;
|
|
CSparse::SparseMV(x,z1,b1);
|
|
vv=means.Dot(solver.m_X.Col(k)+0);
|
|
b1-=vv;
|
|
//--- Calculate (X-meansX)^T*B1
|
|
CSparse::SparseMTV(x,b1,c1);
|
|
vv=b1.Sum();
|
|
solver.m_AX.Col(k,(c1.ToVector()-means*vv));
|
|
}
|
|
}
|
|
CEigenVDetect::EigSubSpaceOOCStop(solver,s2,v,rep);
|
|
if(npoints!=1)
|
|
s2/=(npoints-1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure is used to store temporaries for |
|
|
//| KMeansGenerateInternal function, so we will be able to reuse them|
|
|
//| during multiple subsequent calls. |
|
|
//+------------------------------------------------------------------+
|
|
struct CKmeansBuffers
|
|
{
|
|
CMatrixDouble m_ct;
|
|
CMatrixDouble m_ctbest;
|
|
CRowInt m_xycbest;
|
|
CRowInt m_xycprev;
|
|
CRowDouble m_d2;
|
|
CRowInt m_csizes;
|
|
CApBuff m_initbuf;
|
|
//---
|
|
CKmeansBuffers(void) {}
|
|
~CKmeansBuffers(void) {}
|
|
//---
|
|
void Copy(const CKmeansBuffers &obj);
|
|
//--- overloading
|
|
void operator=(const CKmeansBuffers &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Copy |
|
|
//+------------------------------------------------------------------+
|
|
void CKmeansBuffers::Copy(const CKmeansBuffers &obj)
|
|
{
|
|
m_ct=obj.m_ct;
|
|
m_ctbest=obj.m_ctbest;
|
|
m_xycbest=obj.m_xycbest;
|
|
m_xycprev=obj.m_xycprev;
|
|
m_d2=obj.m_d2;
|
|
m_csizes=obj.m_csizes;
|
|
m_initbuf=obj.m_initbuf;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure is a clusterization engine. |
|
|
//| You should not try to access its fields directly. |
|
|
//| Use ALGLIB functions in order to work with this object. |
|
|
//+------------------------------------------------------------------+
|
|
struct CClusterizerState
|
|
{
|
|
int m_NPoints;
|
|
int m_nfeatures;
|
|
int m_disttype;
|
|
CMatrixDouble m_xy;
|
|
CMatrixDouble m_d;
|
|
int m_ahcalgo;
|
|
int m_kmeansrestarts;
|
|
int m_kmeansmaxits;
|
|
int m_kmeansinitalgo;
|
|
bool m_kmeansdbgnoits;
|
|
int m_seed;
|
|
CMatrixDouble m_tmpd;
|
|
CApBuff m_distbuf;
|
|
CKmeansBuffers m_kmeanstmp;
|
|
//---
|
|
CClusterizerState(void);
|
|
~CClusterizerState(void) {}
|
|
//---
|
|
void Copy(const CClusterizerState &obj);
|
|
//--- overloading
|
|
void operator=(const CClusterizerState &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CClusterizerState::CClusterizerState(void)
|
|
{
|
|
m_NPoints=0;
|
|
m_nfeatures=0;
|
|
m_disttype=0;
|
|
m_ahcalgo=0;
|
|
m_kmeansrestarts=0;
|
|
m_kmeansmaxits=0;
|
|
m_kmeansinitalgo=0;
|
|
m_kmeansdbgnoits=0;
|
|
m_seed=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CClusterizerState::Copy(const CClusterizerState &obj)
|
|
{
|
|
m_NPoints=obj.m_NPoints;
|
|
m_nfeatures=obj.m_nfeatures;
|
|
m_disttype=obj.m_disttype;
|
|
m_xy=obj.m_xy;
|
|
m_d=obj.m_d;
|
|
m_ahcalgo=obj.m_ahcalgo;
|
|
m_kmeansrestarts=obj.m_kmeansrestarts;
|
|
m_kmeansmaxits=obj.m_kmeansmaxits;
|
|
m_kmeansinitalgo=obj.m_kmeansinitalgo;
|
|
m_kmeansdbgnoits=obj.m_kmeansdbgnoits;
|
|
m_seed=obj.m_seed;
|
|
m_tmpd=obj.m_tmpd;
|
|
m_distbuf=obj.m_distbuf;
|
|
m_kmeanstmp=obj.m_kmeanstmp;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure is used to store results of the agglomerative |
|
|
//| hierarchical clustering (AHC). |
|
|
//| Following information is returned: |
|
|
//| * TerminationType - completion code: |
|
|
//| * 1 for successful completion of algorithm |
|
|
//| * -5 inappropriate combination of clustering algorithm and |
|
|
//| distance function was used. As for now, it is possible |
|
|
//| only when Ward's method is called for dataset with |
|
|
//| non-Euclidean distance function. |
|
|
//| In case negative completion code is returned, other fields of |
|
|
//| report structure are invalid and should not be used. |
|
|
//| * NPoints contains number of points in the original dataset |
|
|
//| * Z contains information about merges performed (see below). |
|
|
//| Z contains indexes from the original (unsorted) dataset and |
|
|
//| it can be used when you need to know what points were merged.|
|
|
//| However, it is not convenient when you want to build a |
|
|
//| dendrograd (see below). |
|
|
//| * if you want to build dendrogram, you can use Z, but it is not|
|
|
//| good option, because Z contains indexes from unsorted |
|
|
//| dataset. Dendrogram built from such dataset is likely to |
|
|
//| have intersections. So, you have to reorder you points before|
|
|
//| building dendrogram. |
|
|
//| Permutation which reorders point is returned in P. Another |
|
|
//| representation of merges, which is more convenient for dendorgram|
|
|
//| construction, is returned in PM. |
|
|
//| * more information on format of Z, P and PM can be found below |
|
|
//| and in the examples from ALGLIB Reference Manual. |
|
|
//| FORMAL DESCRIPTION OF FIELDS: |
|
|
//| NPoints - number of points |
|
|
//| Z - array[NPoints-1,2], contains indexes of clusters|
|
|
//| linked in pairs to form clustering tree. I-th |
|
|
//| row corresponds to I-th merge: |
|
|
//| * Z[I,0] - index of the first cluster to merge |
|
|
//| * Z[I,1] - index of the second cluster to merge |
|
|
//| * Z[I,0]<Z[I,1] |
|
|
//| * clusters are numbered from 0 to 2*NPoints-2, with|
|
|
//| indexes from 0 to NPoints-1 corresponding to |
|
|
//| points of the original dataset, and indexes from |
|
|
//| NPoints to 2*NPoints-2 correspond to clusters |
|
|
//| generated by subsequent merges (I-th row of Z |
|
|
//| creates cluster with index NPoints+I). |
|
|
//| IMPORTANT: indexes in Z[] are indexes in the ORIGINAL, unsorted |
|
|
//| dataset. In addition to Z algorithm outputs |
|
|
//| permutation which rearranges points in such way that |
|
|
//| subsequent merges are performed on adjacent points |
|
|
//| (such order is needed if you want to build |
|
|
//| dendrogram). However, indexes in Z are related to |
|
|
//| original, unrearranged sequence of points. |
|
|
//| P - array[NPoints], permutation which reorders points |
|
|
//| for dendrogram construction. P[i] contains index |
|
|
//| of the position where we should move I-th point |
|
|
//| of the original dataset in order to apply merges |
|
|
//| PZ/PM. |
|
|
//| PZ - same as Z, but for permutation of points given by P|
|
|
//| The only thing which changed are indexes of the |
|
|
//| original points; indexes of clusters remained same.|
|
|
//| MergeDist- array[NPoints-1], contains distances between |
|
|
//| clusters being merged (MergeDist[i] correspond to |
|
|
//| merge stored in Z[i,...]): |
|
|
//| * CLINK, SLINK and average linkage algorithms |
|
|
//| report "raw", unmodified distance metric. |
|
|
//| * Ward's method reports weighted intra-cluster |
|
|
//| variance, which is equal to |
|
|
//| ||Ca-Cb||^2 * Sa*Sb/(Sa+Sb). |
|
|
//| Here A and B are clusters being merged, Ca is a |
|
|
//| center of A, Cb is a center of B, Sa is a size |
|
|
//| of A, Sb is a size of B. |
|
|
//| PM - array[NPoints-1,6], another representation of |
|
|
//| merges, which is suited for dendrogram construction|
|
|
//| It deals with rearranged points (permutation P is |
|
|
//| applied) and represents merges in a form which |
|
|
//| different from one used by Z. For each I from 0 to |
|
|
//| NPoints-2, I-th row of PM represents merge |
|
|
//| performed on two clusters C0 and C1. Here: |
|
|
//| * C0 contains points with indexes PM[I,0]...PM[I,1]|
|
|
//| * C1 contains points with indexes PM[I,2]...PM[I,3]|
|
|
//| * indexes stored in PM are given for dataset sorted|
|
|
//| according to permutation P |
|
|
//| * PM[I,1]=PM[I,2]-1 (only adjacent clusters are |
|
|
//| merged) |
|
|
//| * PM[I,0]<=PM[I,1], PM[I,2]<=PM[I,3], i.e. both |
|
|
//| clusters contain at least one point |
|
|
//| *heights of "subdendrograms" corresponding to |
|
|
//| C0/C1 are stored in PM[I,4] and PM[I,5]. |
|
|
//| Subdendrograms corresponding to single-point |
|
|
//| clusters have height=0. Dendrogram of the merge |
|
|
//| result has height H=max(H0,H1)+1. |
|
|
//| NOTE: there is one-to-one correspondence between merges described|
|
|
//| by Z and PM. I-th row of Z describes same merge of clusters|
|
|
//| as I-th row of PM, with "left" cluster from Z corresponding|
|
|
//| to the "left" one from PM. |
|
|
//+------------------------------------------------------------------+
|
|
struct CAHCReport
|
|
{
|
|
int m_terminationtype;
|
|
int m_NPoints;
|
|
CRowInt m_p;
|
|
CMatrixInt m_z;
|
|
CMatrixInt m_pz;
|
|
CMatrixInt m_pm;
|
|
CRowDouble m_mergedist;
|
|
//---
|
|
CAHCReport(void) { m_terminationtype=0; m_NPoints=0; }
|
|
~CAHCReport(void) {}
|
|
//---
|
|
void Copy(const CAHCReport &obj);
|
|
//--- overloading
|
|
void operator=(const CAHCReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CAHCReport::Copy(const CAHCReport &obj)
|
|
{
|
|
m_terminationtype=obj.m_terminationtype;
|
|
m_NPoints=obj.m_NPoints;
|
|
m_p=obj.m_p;
|
|
m_z=obj.m_z;
|
|
m_pz=obj.m_pz;
|
|
m_pm=obj.m_pm;
|
|
m_mergedist=obj.m_mergedist;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This structure is used to store results of the k-means clustering|
|
|
//| algorithm. |
|
|
//| Following information is always returned: |
|
|
//| * NPoints contains number of points in the original dataset |
|
|
//| * TerminationType contains completion code, negative on |
|
|
//| failure, positive on success |
|
|
//| * K contains number of clusters |
|
|
//| For positive TerminationType we return: |
|
|
//| * NFeatures contains number of variables in the original |
|
|
//| dataset |
|
|
//| * C, which contains centers found by algorithm |
|
|
//| * CIdx, which maps points of the original dataset to clusters |
|
|
//| FORMAL DESCRIPTION OF FIELDS: |
|
|
//| NPoints - number of points, >=0 |
|
|
//| NFeatures - number of variables, >=1 |
|
|
//| TerminationType completion code: |
|
|
//| * -5 if distance type is anything different from |
|
|
//| Euclidean metric |
|
|
//| * -3 for degenerate dataset: a) less than K distinct |
|
|
//| points, b) K=0 for non-empty dataset. |
|
|
//| * +1 for successful completion |
|
|
//| K - number of clusters |
|
|
//| C - array[K,NFeatures], rows of the array store centers|
|
|
//| CIdx - array[NPoints], which contains cluster indexes |
|
|
//| IterationsCount actual number of iterations performed by |
|
|
//| clusterizer. If algorithm performed more than one |
|
|
//| random restart, total number of iterations is |
|
|
//| returned. |
|
|
//| Energy - merit function, "energy", sum of squared deviations|
|
|
//| from cluster centers |
|
|
//+------------------------------------------------------------------+
|
|
struct CKmeansReport
|
|
{
|
|
int m_NPoints;
|
|
int m_nfeatures;
|
|
int m_terminationtype;
|
|
int m_iterationscount;
|
|
double m_energy;
|
|
int m_k;
|
|
CMatrixDouble m_c;
|
|
CRowInt m_cidx;
|
|
//---
|
|
CKmeansReport(void);
|
|
~CKmeansReport(void) {}
|
|
//---
|
|
void Copy(const CKmeansReport &obj);
|
|
//--- overloading
|
|
void operator=(const CKmeansReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CKmeansReport::CKmeansReport(void)
|
|
{
|
|
m_NPoints=0;
|
|
m_nfeatures=0;
|
|
m_terminationtype=0;
|
|
m_iterationscount=0;
|
|
m_energy=0;
|
|
m_k=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CKmeansReport::Copy(const CKmeansReport &obj)
|
|
{
|
|
m_NPoints=obj.m_NPoints;
|
|
m_nfeatures=obj.m_nfeatures;
|
|
m_terminationtype=obj.m_terminationtype;
|
|
m_iterationscount=obj.m_iterationscount;
|
|
m_energy=obj.m_energy;
|
|
m_k=obj.m_k;
|
|
m_c=obj.m_c;
|
|
m_cidx=obj.m_cidx;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CClustering
|
|
{
|
|
public:
|
|
//--- constants
|
|
static const int m_kmeansblocksize;
|
|
static const int m_kmeansparalleldim;
|
|
static const int m_kmeansparallelk;
|
|
static const double m_complexitymultiplier;
|
|
|
|
static void ClusterizerCreate(CClusterizerState &s);
|
|
static void ClusterizerSetPoints(CClusterizerState &s,CMatrixDouble &xy,int npoints,int nfeatures,int disttype);
|
|
static void ClusterizerSetDistances(CClusterizerState &s,CMatrixDouble &d,int npoints,bool IsUpper);
|
|
static void ClusterizerSetAHCAlgo(CClusterizerState &s,int algo);
|
|
static void ClusterizerSetKMeansLimits(CClusterizerState &s,int restarts,int maxits);
|
|
static void ClusterizerSetKMeansInit(CClusterizerState &s,int initalgo);
|
|
static void ClusterizerSetSeed(CClusterizerState &s,int seed);
|
|
static void ClusterizerRunAHC(CClusterizerState &s,CAHCReport &rep);
|
|
static void ClusterizerRunKMeans(CClusterizerState &s,int k,CKmeansReport &rep);
|
|
static void ClusterizerGetDistances(CMatrixDouble &xy,int npoints,int nfeatures,int disttype,CMatrixDouble &d);
|
|
static void ClusterizerGetDistancesBuf(CApBuff &buf,CMatrixDouble &xy,int npoints,int nfeatures,int disttype,CMatrixDouble &d);
|
|
static void ClusterizerGetKClusters(CAHCReport &rep,int k,CRowInt &cidx,CRowInt &cz);
|
|
static void ClusterizerSeparatedByDist(CAHCReport &rep,double r,int &k,CRowInt &cidx,CRowInt &cz);
|
|
static void ClusterizerSeparatedByCorr(CAHCReport &rep,double r,int &k,CRowInt &cidx,CRowInt &cz);
|
|
static void KMeansGenerateInternal(CMatrixDouble &xy,int npoints,int nvars,int k,int initalgo,int seed,int maxits,
|
|
int restarts,bool kmeansdbgnoits,int &info,int &iterationscount,CMatrixDouble &ccol,
|
|
bool needccol,CMatrixDouble &crow,bool needcrow,CRowInt &xyc,double &energy,CKmeansBuffers &buf);
|
|
static void KMeansUpdateDistances(CMatrixDouble &xy,int idx0,int idx1,int nvars,CMatrixDouble &ct,int cidx0,int cidx1,CRowInt &xyc,CRowDouble &xydist2);
|
|
|
|
private:
|
|
static void SelectInitialCenters(CMatrixDouble &xy,int npoints,int nvars,int initalgo,CHighQualityRandState &rs,int k,CMatrixDouble &ct,CApBuff &initbuf);
|
|
static bool FixCenters(CMatrixDouble &xy,int npoints,int nvars,CMatrixDouble &ct,int k,CApBuff &initbuf);
|
|
static void ClusterizerRunAHCInternal(CClusterizerState &s,CMatrixDouble &d,CAHCReport &rep);
|
|
static void EvaluateDistanceMatrixRec(CMatrixDouble &xy,int nfeatures,int disttype,CMatrixDouble &d,int i0,int i1,int j0,int j1);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
const int CClustering::m_kmeansblocksize=32;
|
|
const int CClustering::m_kmeansparalleldim=8;
|
|
const int CClustering::m_kmeansparallelk=4;
|
|
const double CClustering::m_complexitymultiplier=1.0;
|
|
//+------------------------------------------------------------------+
|
|
//| This function initializes clusterizer object. Newly initialized |
|
|
//| object is empty, i.e. it does not contain dataset. You should |
|
|
//| use it as follows: |
|
|
//| 1. creation |
|
|
//| 2. dataset is added with ClusterizerSetPoints() |
|
|
//| 3. additional parameters are set |
|
|
//| 3. clusterization is performed with one of the clustering |
|
|
//| functions |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerCreate(CClusterizerState &s)
|
|
{
|
|
s.m_NPoints=0;
|
|
s.m_nfeatures=0;
|
|
s.m_disttype=2;
|
|
s.m_ahcalgo=0;
|
|
s.m_kmeansrestarts=1;
|
|
s.m_kmeansmaxits=0;
|
|
s.m_kmeansinitalgo=0;
|
|
s.m_kmeansdbgnoits=false;
|
|
s.m_seed=1;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function adds dataset to the clusterizer structure. |
|
|
//| This function overrides all previous calls of |
|
|
//| ClusterizerSetPoints() or ClusterizerSetDistances(). |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - clusterizer state, initialized by |
|
|
//| ClusterizerCreate() |
|
|
//| XY - array[NPoints,NFeatures], dataset |
|
|
//| NPoints - number of points, >=0 |
|
|
//| NFeatures- number of features, >=1 |
|
|
//| DistType - distance function: |
|
|
//| * 0 Chebyshev distance (L-inf norm) |
|
|
//| * 1 city block distance (L1 norm) |
|
|
//| * 2 Euclidean distance (L2 norm), non-squared |
|
|
//| * 10 Pearson correlation: |
|
|
//| dist(a,b) = 1-corr(a,b) |
|
|
//| * 11 Absolute Pearson correlation: |
|
|
//| dist(a,b) = 1-|corr(a,b)| |
|
|
//| * 12 Uncentered Pearson correlation (cosine of |
|
|
//| the angle): dist(a,b) = a'*b/(|a|*|b|) |
|
|
//| * 13 Absolute uncentered Pearson correlation |
|
|
//| dist(a,b) = |a'*b|/(|a|*|b|) |
|
|
//| * 20 Spearman rank correlation: |
|
|
//| dist(a,b) = 1-rankcorr(a,b) |
|
|
//| * 21 Absolute Spearman rank correlation |
|
|
//| dist(a,b) = 1-|rankcorr(a,b)| |
|
|
//| NOTE 1: different distance functions have different performance |
|
|
//| penalty: |
|
|
//| * Euclidean or Pearson correlation distances are |
|
|
//| the fastest ones |
|
|
//| * Spearman correlation distance function is a bit slower |
|
|
//| * city block and Chebyshev distances are order |
|
|
//| of magnitude slower |
|
|
//| The reason behing difference in performance is that |
|
|
//| correlation-based distance functions are computed using |
|
|
//| optimized linear algebra kernels, while Chebyshev and |
|
|
//| city block distance functions are computed using simple |
|
|
//| nested loops with two branches at each iteration. |
|
|
//| NOTE 2: different clustering algorithms have different |
|
|
//| limitations: |
|
|
//| * agglomerative hierarchical clustering algorithms may |
|
|
//| be used with any kind of distance metric |
|
|
//| * k-means++ clustering algorithm may be used only with |
|
|
//| Euclidean distance function |
|
|
//| Thus, list of specific clustering algorithms you may use |
|
|
//| depends on distance function you specify when you set |
|
|
//| your dataset. |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerSetPoints(CClusterizerState &s,
|
|
CMatrixDouble &xy,
|
|
int npoints,
|
|
int nfeatures,
|
|
int disttype)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(disttype==0 || disttype==1 || disttype==2 || disttype==10 || disttype==11 ||
|
|
disttype==12 || disttype==13 || disttype==20 || disttype==21,__FUNCTION__": incorrect DistType"))
|
|
return;
|
|
if(!CAp::Assert(npoints>=0,__FUNCTION__": NPoints<0"))
|
|
return;
|
|
if(!CAp::Assert(nfeatures>=1,__FUNCTION__": NFeatures<1"))
|
|
return;
|
|
if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": Rows(XY)<NPoints"))
|
|
return;
|
|
if(!CAp::Assert(xy.Cols()>=nfeatures,__FUNCTION__": Cols(XY)<NFeatures"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nfeatures),__FUNCTION__": XY contains NAN/INF"))
|
|
return;
|
|
|
|
s.m_NPoints=npoints;
|
|
s.m_nfeatures=nfeatures;
|
|
s.m_disttype=disttype;
|
|
s.m_xy=xy;
|
|
s.m_xy.Resize(npoints,nfeatures);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function adds dataset given by distance matrix to the |
|
|
//| clusterizer structure. It is important that dataset is not given |
|
|
//| explicitly - only distance matrix is given. |
|
|
//| This function overrides all previous calls of |
|
|
//| ClusterizerSetPoints() or ClusterizerSetDistances(). |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - clusterizer state, initialized by |
|
|
//| ClusterizerCreate() |
|
|
//| D - array[NPoints,NPoints], distance matrix given by |
|
|
//| its upper or lower triangle (main diagonal is |
|
|
//| ignored because its entries are expected to |
|
|
//| be zero). |
|
|
//| NPoints - number of points |
|
|
//| IsUpper - whether upper or lower triangle of D is given. |
|
|
//| NOTE 1: different clustering algorithms have different |
|
|
//| limitations: |
|
|
//| * agglomerative hierarchical clustering algorithms may |
|
|
//| be used with any kind of distance metric, including |
|
|
//| one which is given by distance matrix |
|
|
//| * k-means++ clustering algorithm may be used only with |
|
|
//| Euclidean distance function and explicitly given |
|
|
//| points - it can not be used with dataset given by |
|
|
//| distance matrix. Thus, if you call this function, you |
|
|
//| will be unable to use k-means clustering algorithm |
|
|
//| to process your problem. |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerSetDistances(CClusterizerState &s,
|
|
CMatrixDouble &d,
|
|
int npoints,
|
|
bool IsUpper)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(npoints>=0,__FUNCTION__": NPoints<0"))
|
|
return;
|
|
if(!CAp::Assert(d.Rows()>=npoints,__FUNCTION__": Rows(D)<NPoints"))
|
|
return;
|
|
if(!CAp::Assert(d.Cols()>=npoints,__FUNCTION__": Cols(D)<NPoints"))
|
|
return;
|
|
//--- initialization
|
|
s.m_NPoints=npoints;
|
|
s.m_nfeatures=0;
|
|
s.m_disttype=-1;
|
|
s.m_d=d;
|
|
s.m_d.Resize(npoints,npoints);
|
|
s.m_d.Diag(vector<double>::Zeros(npoints));
|
|
if(IsUpper)
|
|
s.m_d=s.m_d.TriU()+0;
|
|
else
|
|
s.m_d=s.m_d.TriL()+0;
|
|
s.m_d+=s.m_d.Transpose()+0;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(s.m_d,npoints,npoints) || s.m_d.Min()>=0,__FUNCTION__": D contains infinite,NAN or negative elements"))
|
|
return;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets agglomerative hierarchical clustering |
|
|
//| algorithm |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - clusterizer state, initialized by ClusterizerCreate() |
|
|
//| Algo - algorithm type: |
|
|
//| * 0 complete linkage(default algorithm) |
|
|
//| * 1 single linkage |
|
|
//| * 2 unweighted average linkage |
|
|
//| * 3 weighted average linkage |
|
|
//| * 4 Ward's method |
|
|
//| NOTE: Ward's method works correctly only with Euclidean distance,|
|
|
//| that's why algorithm will return negative termination |
|
|
//| code(failure) for any other distance type. |
|
|
//| It is possible, however, to use this method with user - supplied |
|
|
//| distance matrix. It is your responsibility to pass one which was |
|
|
//| calculated with Euclidean distance function. |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerSetAHCAlgo(CClusterizerState &s,int algo)
|
|
{
|
|
if(!CAp::Assert(algo==0 || algo==1 || algo==2 || algo==3 || algo==4,__FUNCTION__": incorrect algorithm type"))
|
|
return;
|
|
s.m_ahcalgo=algo;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets k-means properties: |
|
|
//| number of restarts and maximum |
|
|
//| number of iterations per one run. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - clusterizer state, initialized by |
|
|
//| ClusterizerCreate() |
|
|
//| Restarts - restarts count, >= 1. |
|
|
//| k-means++ algorithm performs several restarts |
|
|
//| and chooses best set of centers(one with minimum |
|
|
//| squared distance). |
|
|
//| MaxIts - maximum number of k-means iterations performed |
|
|
//| during one run. >= 0, zero value means that |
|
|
//| algorithm performs unlimited number of iterations. |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerSetKMeansLimits(CClusterizerState &s,
|
|
int restarts,
|
|
int maxits)
|
|
{
|
|
if(!CAp::Assert(restarts>=1,__FUNCTION__": Restarts<=0"))
|
|
return;
|
|
if(!CAp::Assert(maxits>=0,__FUNCTION__": MaxIts<0"))
|
|
return;
|
|
s.m_kmeansrestarts=restarts;
|
|
s.m_kmeansmaxits=maxits;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets k-means initialization algorithm. Several |
|
|
//| different algorithms can be chosen, including k-means++. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - clusterizer state, initialized by |
|
|
//| ClusterizerCreate() |
|
|
//| InitAlgo - initialization algorithm: |
|
|
//| * 0 automatic selection(different versions of ALGLIB |
|
|
//| may select different algorithms) |
|
|
//| * 1 random initialization |
|
|
//| * 2 k-means++ initialization(best quality of initial |
|
|
//| centers, but long non-parallelizable initialization |
|
|
//| phase with bad cache locality) |
|
|
//| *3 "fast-greedy" algorithm with efficient, easy to |
|
|
//| parallelize initialization. Quality of initial centers |
|
|
//| is somewhat worse than that of k-means++. This |
|
|
//| algorithm is a default one in the current version of |
|
|
//| ALGLIB. |
|
|
//| *-1 "debug" algorithm which always selects first K rows |
|
|
//| of dataset; this algorithm is used for debug purposes |
|
|
//| only. Do not use it in the industrial code! |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerSetKMeansInit(CClusterizerState &s,
|
|
int initalgo)
|
|
{
|
|
if(!CAp::Assert(initalgo>=-1 && initalgo<=3,__FUNCTION__": InitAlgo is incorrect"))
|
|
return;
|
|
s.m_kmeansinitalgo=initalgo;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets seed which is used to initialize internal RNG.|
|
|
//| By default, deterministic seed is used - same for each run of |
|
|
//| clusterizer. If you specify non-deterministic seed value, then |
|
|
//| some algorithms which depend on random initialization(in current |
|
|
//| version : k-means) may return slightly different results after |
|
|
//| each run. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - clusterizer state, initialized by |
|
|
//| ClusterizerCreate() |
|
|
//| Seed - seed: |
|
|
//| * positive values = use deterministic seed for each|
|
|
//| run of algorithms which depend on random |
|
|
//| initialization |
|
|
//| * zero or negative values = use non-deterministic |
|
|
//| seed |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerSetSeed(CClusterizerState &s,int seed)
|
|
{
|
|
s.m_seed=seed;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs agglomerative hierarchical clustering |
|
|
//| NOTE: Agglomerative hierarchical clustering algorithm has two |
|
|
//| phases: distance matrix calculation and clustering itself. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - clusterizer state, initialized by |
|
|
//| ClusterizerCreate() |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Rep - clustering results; see description of AHCReport |
|
|
//| structure for more information. |
|
|
//| NOTE 1: hierarchical clustering algorithms require large amounts |
|
|
//| of memory. In particular, this implementation needs |
|
|
//| sizeof(double) *NPoints^2 bytes, which are used to store |
|
|
//| distance matrix. In case we work with user - supplied |
|
|
//| matrix, this amount is multiplied by 2 (we have to store |
|
|
//| original matrix and to work with its copy). |
|
|
//| For example, problem with 10000 points would require 800M|
|
|
//| of RAM, even when working in a 1-dimensional space. |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerRunAHC(CClusterizerState &s,
|
|
CAHCReport &rep)
|
|
{
|
|
//--- create variables
|
|
int npoints=s.m_NPoints;
|
|
int nfeatures=s.m_nfeatures;
|
|
//--- Fill Rep.NPoints, quick exit when NPoints<=1
|
|
rep.m_NPoints=npoints;
|
|
if(npoints==0)
|
|
{
|
|
rep.m_p.Resize(0);
|
|
rep.m_z.Resize(0,0);
|
|
rep.m_pz.Resize(0,0);
|
|
rep.m_pm.Resize(0,0);
|
|
rep.m_mergedist.Resize(0);
|
|
rep.m_terminationtype=1;
|
|
return;
|
|
}
|
|
if(npoints==1)
|
|
{
|
|
rep.m_p.Resize(1);
|
|
rep.m_z.Resize(0,0);
|
|
rep.m_pz.Resize(0,0);
|
|
rep.m_pm.Resize(0,0);
|
|
rep.m_mergedist.Resize(0);
|
|
rep.m_p.Set(0,0);
|
|
rep.m_terminationtype=1;
|
|
return;
|
|
}
|
|
//--- More than one point
|
|
if(s.m_disttype==-1)
|
|
{
|
|
//--- Run clusterizer with user-supplied distance matrix
|
|
ClusterizerRunAHCInternal(s,s.m_d,rep);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//--- Check combination of AHC algo and distance type
|
|
if(s.m_ahcalgo==4 && s.m_disttype!=2)
|
|
{
|
|
rep.m_terminationtype=-5;
|
|
return;
|
|
}
|
|
//--- Build distance matrix D.
|
|
ClusterizerGetDistancesBuf(s.m_distbuf,s.m_xy,npoints,nfeatures,s.m_disttype,s.m_tmpd);
|
|
//--- Run clusterizer
|
|
ClusterizerRunAHCInternal(s,s.m_tmpd,rep);
|
|
return;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs clustering by k-means++ algorithm. |
|
|
//| You may change algorithm properties by calling: |
|
|
//| * ClusterizerSetKMeansLimits() to change number of restarts |
|
|
//| or iterations |
|
|
//| * ClusterizerSetKMeansInit() to change initialization |
|
|
//| algorithm |
|
|
//| By default, one restart and unlimited number of iterations are |
|
|
//| used. Initialization algorithm is chosen automatically. |
|
|
//| NOTE: k-means clustering algorithm has two phases: selection of |
|
|
//| initial centers and clustering itself. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - clusterizer state, initialized by |
|
|
//| ClusterizerCreate() |
|
|
//| K - number of clusters, K >= 0. |
|
|
//| K can be zero only when algorithm is called for |
|
|
//| empty dataset, in this case completion code is set |
|
|
//| to success(+1). |
|
|
//| If K = 0 and dataset size is non-zero, we can not |
|
|
//| meaningfully assign points to some center(there are|
|
|
//| no centers because K = 0) and return -3 as |
|
|
//| completion code (failure). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Rep - clustering results; see description of KMeansReport|
|
|
//| structure for more information. |
|
|
//| NOTE 1: k-means clustering can be performed only for datasets |
|
|
//| with Euclidean distance function. Algorithm will return |
|
|
//| negative completion code in Rep.TerminationType in case |
|
|
//| dataset was added to clusterizer with DistType other |
|
|
//| than Euclidean (or dataset was specified by distance |
|
|
//| matrix instead of explicitly given points). |
|
|
//| NOTE 2: by default, k-means uses non-deterministic seed to |
|
|
//| initialize RNG which is used to select initial centers. |
|
|
//| As result, each run of algorithm may return different |
|
|
//| values. If you need deterministic behavior, use |
|
|
//| ClusterizerSetSeed() function. |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerRunKMeans(CClusterizerState &s,
|
|
int k,
|
|
CKmeansReport &rep)
|
|
{
|
|
//--- create variables
|
|
CMatrixDouble dummy;
|
|
//--- check
|
|
if(!CAp::Assert(k>=0,__FUNCTION__": K<0"))
|
|
return;
|
|
//--- Incorrect distance type
|
|
if(s.m_disttype!=2)
|
|
{
|
|
rep.m_NPoints=s.m_NPoints;
|
|
rep.m_terminationtype=-5;
|
|
rep.m_k=k;
|
|
rep.m_iterationscount=0;
|
|
rep.m_energy=0.0;
|
|
return;
|
|
}
|
|
//--- K>NPoints or (K=0 and NPoints>0)
|
|
if(k>s.m_NPoints || (k==0 && s.m_NPoints>0))
|
|
{
|
|
rep.m_NPoints=s.m_NPoints;
|
|
rep.m_terminationtype=-3;
|
|
rep.m_k=k;
|
|
rep.m_iterationscount=0;
|
|
rep.m_energy=0.0;
|
|
return;
|
|
}
|
|
//--- No points
|
|
if(s.m_NPoints==0)
|
|
{
|
|
rep.m_NPoints=0;
|
|
rep.m_terminationtype=1;
|
|
rep.m_k=k;
|
|
rep.m_iterationscount=0;
|
|
rep.m_energy=0.0;
|
|
return;
|
|
}
|
|
//--- Normal case:
|
|
//--- 1<=K<=NPoints, Euclidean distance
|
|
rep.m_NPoints=s.m_NPoints;
|
|
rep.m_nfeatures=s.m_nfeatures;
|
|
rep.m_k=k;
|
|
rep.m_NPoints=s.m_NPoints;
|
|
rep.m_nfeatures=s.m_nfeatures;
|
|
KMeansGenerateInternal(s.m_xy,s.m_NPoints,s.m_nfeatures,k,s.m_kmeansinitalgo,s.m_seed,s.m_kmeansmaxits,
|
|
s.m_kmeansrestarts,s.m_kmeansdbgnoits,rep.m_terminationtype,rep.m_iterationscount,dummy,
|
|
false,rep.m_c,true,rep.m_cidx,rep.m_energy,s.m_kmeanstmp);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns distance matrix for dataset |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - array[NPoints, NFeatures], dataset |
|
|
//| NPoints - number of points, >= 0 |
|
|
//| NFeatures- number of features, >= 1 |
|
|
//| DistType - distance function: |
|
|
//| * 0 Chebyshev distance(L - inf norm) |
|
|
//| * 1 city block distance(L1 norm) |
|
|
//| * 2 Euclidean distance(L2 norm, non - squared) |
|
|
//| * 10 Pearson correlation: |
|
|
//| dist(a, b) = 1 - corr(a, b) |
|
|
//| * 11 Absolute Pearson correlation: |
|
|
//| dist(a, b) = 1 - |corr(a, b)| |
|
|
//| * 12 Uncentered Pearson correlation(cosine of |
|
|
//| the angle): dist(a, b) = a'*b/(|a|*|b|) |
|
|
//| * 13 Absolute uncentered Pearson correlation |
|
|
//| dist(a, b) = |a'*b|/(|a|*|b|) |
|
|
//| * 20 Spearman rank correlation: |
|
|
//| dist(a, b) = 1 - rankcorr(a, b) |
|
|
//| * 21 Absolute Spearman rank correlation |
|
|
//| dist(a, b) = 1 - |rankcorr(a, b)| |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| D - array[NPoints, NPoints], distance matrix (full |
|
|
//| matrix is returned, with lower and upper triangles)|
|
|
//| NOTE: different distance functions have different performance |
|
|
//| penalty: |
|
|
//| * Euclidean or Pearson correlation distances are the fastest|
|
|
//| ones |
|
|
//| * Spearman correlation distance function is a bit slower |
|
|
//| * city block and Chebyshev distances are order of magnitude |
|
|
//| slower |
|
|
//| The reason behing difference in performance is that correlation -|
|
|
//| based distance functions are computed using optimized linear |
|
|
//| algebra kernels, while Chebyshev and city block distance |
|
|
//| functions are computed using simple nested loops with two |
|
|
//| branches at each iteration. |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerGetDistances(CMatrixDouble &xy,
|
|
int npoints,
|
|
int nfeatures,
|
|
int disttype,
|
|
CMatrixDouble &d)
|
|
{
|
|
CApBuff buf;
|
|
|
|
d.Resize(0,0);
|
|
//--- check
|
|
if(!CAp::Assert(nfeatures>=1,__FUNCTION__": NFeatures<1"))
|
|
return;
|
|
if(!CAp::Assert(npoints>=0,__FUNCTION__": NPoints<1"))
|
|
return;
|
|
if(!CAp::Assert(disttype==0 || disttype==1 || disttype==2 || disttype==10 || disttype==11 ||
|
|
disttype==12 || disttype==13 || disttype==20 || disttype==21,__FUNCTION__": incorrect DistType"))
|
|
return;
|
|
if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": Rows(XY)<NPoints"))
|
|
return;
|
|
if(!CAp::Assert(xy.Cols()>=nfeatures,__FUNCTION__": Cols(XY)<NFeatures"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nfeatures),__FUNCTION__": XY contains NAN/INF"))
|
|
return;
|
|
ClusterizerGetDistancesBuf(buf,xy,npoints,nfeatures,disttype,d);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Buffered version of ClusterizerGetDistances() which reuses |
|
|
//| previously allocated space. |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerGetDistancesBuf(CApBuff &buf,
|
|
CMatrixDouble &xy,
|
|
int npoints,
|
|
int nfeatures,
|
|
int disttype,
|
|
CMatrixDouble &d)
|
|
{
|
|
//--- create variables
|
|
double v=0;
|
|
double vv=0;
|
|
double vr=0;
|
|
//--- check
|
|
if(!CAp::Assert(nfeatures>=1,__FUNCTION__": NFeatures<1"))
|
|
return;
|
|
if(!CAp::Assert(npoints>=0,__FUNCTION__": NPoints<1"))
|
|
return;
|
|
if(!CAp::Assert(disttype==0 || disttype==1 || disttype==2 || disttype==10 || disttype==11 || disttype==12 ||
|
|
disttype==13 || disttype==20 || disttype==21,__FUNCTION__": incorrect DistType"))
|
|
return;
|
|
if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__": Rows(XY)<NPoints"))
|
|
return;
|
|
if(!CAp::Assert(xy.Cols()>=nfeatures,__FUNCTION__": Cols(XY)<NFeatures"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nfeatures),__FUNCTION__": XY contains NAN/INF"))
|
|
return;
|
|
//--- Quick exit
|
|
if(npoints==0)
|
|
return;
|
|
if(npoints==1)
|
|
{
|
|
CApServ::RMatrixSetLengthAtLeast(d,1,1);
|
|
d.Set(0,0,0);
|
|
return;
|
|
}
|
|
//--- Build distance matrix D.
|
|
if(disttype==0 || disttype==1)
|
|
{
|
|
//--- Chebyshev or city-block distances:
|
|
//--- * recursively calculate upper triangle (with main diagonal)
|
|
//--- * copy it to the bottom part of the matrix
|
|
CApServ::RMatrixSetLengthAtLeast(d,npoints,npoints);
|
|
EvaluateDistanceMatrixRec(xy,nfeatures,disttype,d,0,npoints,0,npoints);
|
|
CAblas::RMatrixEnforceSymmetricity(d,npoints,true);
|
|
return;
|
|
}
|
|
if(disttype==2)
|
|
{
|
|
//--- Euclidean distance
|
|
//--- NOTE: parallelization is done within RMatrixSYRK
|
|
d.Resize(npoints,npoints);
|
|
buf.m_rm0=xy;
|
|
buf.m_rm0.Resize(npoints,nfeatures);
|
|
buf.m_ra1=buf.m_rm0.Mean(0)+0;
|
|
for(int i=0; i<npoints; i++)
|
|
buf.m_rm0.Row(i,buf.m_rm0[i]-(buf.m_ra1+0));
|
|
CAblas::RMatrixSyrk(npoints,nfeatures,1.0,buf.m_rm0,0,0,0,0.0,d,0,0,true);
|
|
buf.m_ra0=d.Diag(0)+0;
|
|
d.Diag(vector<double>::Zeros(npoints));
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
for(int j=i+1; j<npoints; j++)
|
|
{
|
|
v=MathSqrt(MathMax(buf.m_ra0[i]+buf.m_ra0[j]-2*d.Get(i,j),0.0));
|
|
d.Set(i,j,v);
|
|
}
|
|
}
|
|
CAblas::RMatrixEnforceSymmetricity(d,npoints,true);
|
|
return;
|
|
}
|
|
if(disttype==10 || disttype==11)
|
|
{
|
|
//--- Absolute/nonabsolute Pearson correlation distance
|
|
//--- NOTE: parallelization is done within PearsonCorrM, which calls RMatrixSYRK internally
|
|
d.Resize(npoints,npoints);
|
|
buf.m_rm0=xy;
|
|
buf.m_rm0.Resize(npoints,nfeatures);
|
|
buf.m_ra0=buf.m_rm0.Mean(1)+0;
|
|
for(int i=0; i<npoints; i++)
|
|
buf.m_rm0.Row(i,buf.m_rm0[i]-buf.m_ra0[i]);
|
|
CAblas::RMatrixSyrk(npoints,nfeatures,1.0,buf.m_rm0,0,0,0,0.0,d,0,0,true);
|
|
buf.m_ra0=d.Diag()+0;
|
|
d.Diag(vector<double>::Zeros(npoints));
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
for(int j=i+1; j<npoints; j++)
|
|
{
|
|
v=d.Get(i,j)/MathSqrt(buf.m_ra0[i]*buf.m_ra0[j]);
|
|
if(disttype==10)
|
|
v=1-v;
|
|
else
|
|
v=1-MathAbs(v);
|
|
v=MathMax(v,0.0);
|
|
d.Set(i,j,v);
|
|
}
|
|
}
|
|
CAblas::RMatrixEnforceSymmetricity(d,npoints,true);
|
|
return;
|
|
}
|
|
if(disttype==12 || disttype==13)
|
|
{
|
|
//--- Absolute/nonabsolute uncentered Pearson correlation distance
|
|
//--- NOTE: parallelization is done within RMatrixSYRK
|
|
d.Resize(npoints,npoints);
|
|
CAblas::RMatrixSyrk(npoints,nfeatures,1.0,xy,0,0,0,0.0,d,0,0,true);
|
|
buf.m_ra0=d.Diag()+0;
|
|
d.Diag(vector<double>::Zeros(npoints));
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
for(int j=i+1; j<npoints; j++)
|
|
{
|
|
v=d.Get(i,j)/MathSqrt(buf.m_ra0[i]*buf.m_ra0[j]);
|
|
if(disttype==13)
|
|
v=MathAbs(v);
|
|
v=MathMin(v,1.0);
|
|
d.Set(i,j,1-v);
|
|
}
|
|
}
|
|
CAblas::RMatrixEnforceSymmetricity(d,npoints,true);
|
|
return;
|
|
}
|
|
if(disttype==20 || disttype==21)
|
|
{
|
|
//--- Spearman rank correlation
|
|
//--- NOTE: parallelization of correlation matrix is done within
|
|
//--- PearsonCorrM, which calls RMatrixSYRK internally
|
|
d.Resize(npoints,npoints);
|
|
buf.m_rm0=xy;
|
|
buf.m_rm0.Resize(npoints,nfeatures);
|
|
CBaseStat::RankDataCentered(buf.m_rm0,npoints,nfeatures);
|
|
CAblas::RMatrixSyrk(npoints,nfeatures,1.0,buf.m_rm0,0,0,0,0.0,d,0,0,true);
|
|
buf.m_ra0=vector<double>::Zeros(npoints);
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
if(d.Get(i,i)>0.0)
|
|
buf.m_ra0.Set(i,1.0/MathSqrt(d.Get(i,i)));
|
|
}
|
|
d.Diag(vector<double>::Zeros(npoints));
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
v=buf.m_ra0[i];
|
|
for(int j=i+1; j<npoints; j++)
|
|
{
|
|
vv=d.Get(i,j)*v*buf.m_ra0[j];
|
|
if(disttype==20)
|
|
vr=1-vv;
|
|
else
|
|
vr=1-MathAbs(vv);
|
|
if(vr<0.0)
|
|
vr=0.0;
|
|
d.Set(i,j,vr);
|
|
}
|
|
}
|
|
CAblas::RMatrixEnforceSymmetricity(d,npoints,true);
|
|
return;
|
|
}
|
|
if(!CAp::Assert(false))
|
|
return;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function takes as input clusterization report Rep, desired |
|
|
//| clusters count K, and builds top K clusters from hierarchical |
|
|
//| clusterization tree. |
|
|
//| It returns assignment of points to clusters(array of cluster |
|
|
//| indexes). |
|
|
//| INPUT PARAMETERS: |
|
|
//| Rep - report from ClusterizerRunAHC() performed on XY |
|
|
//| K - desired number of clusters, 1 <= K <= NPoints. |
|
|
//| K can be zero only when NPoints = 0. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| CIdx - array[NPoints], I-th element contains cluster |
|
|
//| index(from 0 to K-1) for I-th point of the dataset.|
|
|
//| CZ - array[K]. This array allows to convert cluster |
|
|
//| indexes returned by this function to indexes used |
|
|
//| by Rep.Z. J-th cluster returned by this function |
|
|
//| corresponds to CZ[J]-th cluster stored in |
|
|
//| Rep.Z/PZ/PM. It is guaranteed that CZ[I] < CZ[I+1].|
|
|
//| NOTE: K clusters built by this subroutine are assumed to have no |
|
|
//| hierarchy. Although they were obtained by manipulation with|
|
|
//| top K nodes of dendrogram(i.e. hierarchical decomposition |
|
|
//| of dataset), this function does not return information |
|
|
//| about hierarchy. Each of the clusters stand on its own. |
|
|
//| NOTE: Cluster indexes returned by this function does not |
|
|
//| correspond to indexes returned in Rep.Z/PZ/PM. Either you |
|
|
//| work with hierarchical representation of the dataset |
|
|
//| (dendrogram), or you work with "flat" representation |
|
|
//| returned by this function. Each of representations has its |
|
|
//| own clusters indexing system(former uses [0,2*NPoints-2]), |
|
|
//| while latter uses [0..K-1]), although it is possible to |
|
|
//| perform conversion from one system to another by means of |
|
|
//| CZ array, returned by this function, which allows you to |
|
|
//| convert indexes stored in CIdx to the numeration system |
|
|
//| used by Rep.Z. |
|
|
//| NOTE: this subroutine is optimized for moderate values of K. |
|
|
//| Say, for K=5 it will perform many times faster than for |
|
|
//| K=100. Its worst - case performance is O(N*K), although in |
|
|
//| average case it perform better (up to O(N*log(K))). |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerGetKClusters(CAHCReport &rep,
|
|
int k,
|
|
CRowInt &cidx,
|
|
CRowInt &cz)
|
|
{
|
|
//--- create variables
|
|
int npoints=rep.m_NPoints;
|
|
int i0=0;
|
|
int i1=0;
|
|
int t=0;
|
|
bool presentclusters[];
|
|
CRowInt clusterindexes;
|
|
CRowInt clustersizes;
|
|
CRowInt tmpidx;
|
|
|
|
cidx.Resize(0);
|
|
cz.Resize(0);
|
|
//--- check
|
|
if(!CAp::Assert(npoints>=0,__FUNCTION__": internal error in Rep integrity"))
|
|
return;
|
|
if(!CAp::Assert(k>0,__FUNCTION__": K<=0"))
|
|
return;
|
|
if(!CAp::Assert(k<=npoints,__FUNCTION__": K>NPoints"))
|
|
return;
|
|
if(!CAp::Assert(npoints==rep.m_NPoints,__FUNCTION__": NPoints<>Rep.NPoints"))
|
|
return;
|
|
//--- Quick exit
|
|
if(npoints==0)
|
|
return;
|
|
if(npoints==1)
|
|
{
|
|
int temp[]={0};
|
|
cz=temp;
|
|
cidx=temp;
|
|
return;
|
|
}
|
|
//--- Replay merges, from top to bottom,
|
|
//--- keep track of clusters being present at the moment
|
|
ArrayResize(presentclusters,2*npoints-1);
|
|
tmpidx.Resize(npoints);
|
|
ArrayInitialize(presentclusters,false);
|
|
presentclusters[2*npoints-2]=true;
|
|
tmpidx.Fill(2*npoints-2);
|
|
for(int mergeidx=npoints-2; mergeidx>=npoints-k; mergeidx--)
|
|
{
|
|
//--- Update information about clusters being present at the moment
|
|
presentclusters[npoints+mergeidx]=false;
|
|
presentclusters[rep.m_z.Get(mergeidx,0)]=true;
|
|
presentclusters[rep.m_z.Get(mergeidx,1)]=true;
|
|
//--- Update TmpIdx according to the current state of the dataset
|
|
//--- NOTE: TmpIdx contains cluster indexes from [0..2*NPoints-2];
|
|
//--- we will convert them to [0..K-1] later.
|
|
i0=rep.m_pm.Get(mergeidx,0);
|
|
i1=rep.m_pm.Get(mergeidx,1);
|
|
t=rep.m_z.Get(mergeidx,0);
|
|
tmpidx.Fill(t,i0,i1-i0+1);
|
|
i0=rep.m_pm.Get(mergeidx,2);
|
|
i1=rep.m_pm.Get(mergeidx,3);
|
|
t=rep.m_z.Get(mergeidx,1);
|
|
tmpidx.Fill(t,i0,i1-i0+1);
|
|
}
|
|
//--- Fill CZ - array which allows us to convert cluster indexes
|
|
//--- from one system to another.
|
|
cz.Resize(k);
|
|
clusterindexes.Resize(2*npoints-1);
|
|
t=0;
|
|
for(int i=0; i<2*npoints-1; i++)
|
|
{
|
|
if(presentclusters[i])
|
|
{
|
|
cz.Set(t,i);
|
|
clusterindexes.Set(i,t);
|
|
t++;
|
|
}
|
|
}
|
|
if(!CAp::Assert(t==k,__FUNCTION__": internal error"))
|
|
return;
|
|
//--- Convert indexes stored in CIdx
|
|
cidx.Resize(npoints);
|
|
for(int i=0; i<npoints; i++)
|
|
cidx.Set(i,clusterindexes[tmpidx[rep.m_p[i]]]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function accepts AHC report Rep, desired minimum |
|
|
//| intercluster distance and returns top clusters from hierarchical |
|
|
//| clusterization tree which are separated by distance R or HIGHER. |
|
|
//| It returns assignment of points to clusters (array of cluster |
|
|
//| indexes). |
|
|
//| There is one more function with similar name - |
|
|
//| ClusterizerSeparatedByCorr, which returns clusters with |
|
|
//| intercluster correlation equal to R or LOWER (note: higher for |
|
|
//| distance, lower for correlation). |
|
|
//| INPUT PARAMETERS: |
|
|
//| Rep - report from ClusterizerRunAHC() performed on XY |
|
|
//| R - desired minimum intercluster distance, R >= 0 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| K - number of clusters, 1 <= K <= NPoints |
|
|
//| CIdx - array[NPoints], I-th element contains cluster |
|
|
//| index (from 0 to K-1) for I-th point of the dataset|
|
|
//| CZ - array[K]. This array allows to convert cluster |
|
|
//| indexes returned by this function to indexes used |
|
|
//| by Rep.Z. J-th cluster returned by this function |
|
|
//| corresponds to CZ[J]-th cluster stored in |
|
|
//| Rep.Z/PZ/PM. It is guaranteed that CZ[I] < CZ[I+1].|
|
|
//| NOTE: K clusters built by this subroutine are assumed to have no |
|
|
//| hierarchy. Although they were obtained by manipulation with|
|
|
//| top K nodes of dendrogram (i.e. hierarchical decomposition |
|
|
//| of dataset), this function does not return information |
|
|
//| about hierarchy. Each of the clusters stand on its own. |
|
|
//| NOTE: Cluster indexes returned by this function does not |
|
|
//| correspond to indexes returned in Rep.Z/PZ/PM. Either you |
|
|
//| work with hierarchical representation of the dataset |
|
|
//| (dendrogram), or you work with "flat" representation |
|
|
//| returned by this function. Each of representations has its |
|
|
//| own clusters indexing system (former uses [0,2*NPoints-2]),|
|
|
//| while latter uses [0..K-1]), although it is possible to |
|
|
//| perform conversion from one system to another by means of |
|
|
//| CZ array, returned by this function, which allows you to |
|
|
//| convert indexes stored in CIdx to the numeration system |
|
|
//| used by Rep.Z. |
|
|
//| NOTE: this subroutine is optimized for moderate values of K. Say,|
|
|
//| for K=5 it will perform many times faster than for K=100. |
|
|
//| Its worst - case performance is O(N*K), although in average|
|
|
//| case it perform better (up to O(N*log(K))). |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerSeparatedByDist(CAHCReport &rep,
|
|
double r,
|
|
int &k,
|
|
CRowInt &cidx,
|
|
CRowInt &cz)
|
|
{
|
|
//--- initialization
|
|
k=0;
|
|
cidx.Resize(0);
|
|
cz.Resize(0);
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(r) && r>=0.0,__FUNCTION__": R is infinite or less than 0"))
|
|
return;
|
|
|
|
k=1;
|
|
while(k<rep.m_NPoints && rep.m_mergedist[rep.m_NPoints-1-k]>=r)
|
|
k++;
|
|
ClusterizerGetKClusters(rep,k,cidx,cz);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function accepts AHC report Rep, desired maximum |
|
|
//| intercluster correlation and returns top clusters from |
|
|
//| hierarchical clusterization tree which are separated by |
|
|
//| correlation R or LOWER. |
|
|
//| It returns assignment of points to clusters(array of cluster |
|
|
//| indexes). |
|
|
//| There is one more function with similar name - |
|
|
//| ClusterizerSeparatedByDist, which returns clusters with |
|
|
//| intercluster distance equal to R or HIGHER (note: higher for |
|
|
//| distance, lower for correlation). |
|
|
//| INPUT PARAMETERS: |
|
|
//| Rep - report from ClusterizerRunAHC() performed on XY |
|
|
//| R - desired maximum intercluster correlation, -1<=R<=+1|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| K - number of clusters, 1 <= K <= NPoints |
|
|
//| CIdx - array[NPoints], I-th element contains cluster index|
|
|
//| (from 0 to K-1) for I-th point of the dataset. |
|
|
//| CZ - array[K]. This array allows to convert cluster |
|
|
//| indexes returned by this function to indexes used |
|
|
//| by Rep.Z. J-th cluster returned by this function |
|
|
//| corresponds to CZ[J]-th cluster stored in |
|
|
//| Rep.Z/PZ/PM. It is guaranteed that CZ[I] < CZ[I+1].|
|
|
//| NOTE: K clusters built by this subroutine are assumed to have no |
|
|
//| hierarchy. Although they were obtained by manipulation with|
|
|
//| top K nodes of dendrogram (i.e. hierarchical decomposition |
|
|
//| of dataset), this function does not return information |
|
|
//| about hierarchy. Each of the clusters stand on its own. |
|
|
//| NOTE: Cluster indexes returned by this function does not |
|
|
//| correspond to indexes returned in Rep.Z/PZ/PM. Either you |
|
|
//| work with hierarchical representation of the dataset |
|
|
//| (dendrogram), or you work with "flat" representation |
|
|
//| returned by this function. Each of representations has its |
|
|
//| own clusters indexing system (former uses [0,2*NPoints-2]),|
|
|
//| while latter uses [0..K-1]), although it is possible to |
|
|
//| perform conversion from one system to another by means of |
|
|
//| CZ array, returned by this function, which allows you to |
|
|
//| convert indexes stored in CIdx to the numeration system |
|
|
//| used by Rep.Z. |
|
|
//| NOTE: this subroutine is optimized for moderate values of K. Say,|
|
|
//| for K=5 it will perform many times faster than for K=100. |
|
|
//| Its worst - case performance is O(N*K), although in average|
|
|
//| case it perform better (up to O(N*log(K))). |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerSeparatedByCorr(CAHCReport &rep,
|
|
double r,
|
|
int &k,
|
|
CRowInt &cidx,
|
|
CRowInt &cz)
|
|
{
|
|
k=0;
|
|
cidx.Resize(0);
|
|
cz.Resize(0);
|
|
if(!CAp::Assert(MathIsValidNumber(r) && r>=-1.0 && r<=1.0,__FUNCTION__": R is infinite or less than 0"))
|
|
return;
|
|
k=1;
|
|
while(k<rep.m_NPoints && rep.m_mergedist[rep.m_NPoints-1-k]>=(1.0-r))
|
|
k++;
|
|
ClusterizerGetKClusters(rep,k,cidx,cz);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 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 |
|
|
//| InitAlgo - initialization algorithm: |
|
|
//| * 0 - automatic selection of best algorithm |
|
|
//| * 1 - random selection of centers |
|
|
//| * 2 - k-means++ |
|
|
//| * 3 - fast-greedy init |
|
|
//| * -1 - first K rows of dataset are used (special |
|
|
//| debug algorithm) |
|
|
//| Seed - seed value for internal RNG : |
|
|
//| * positive value is used to initialize RNG in order|
|
|
//| to induce deterministic behavior of algorithm |
|
|
//| * zero or negative value means that random seed is |
|
|
//| generated |
|
|
//| MaxIts - iterations limit or zero for no limit |
|
|
//| Restarts - number of restarts, Restarts >= 1 |
|
|
//| KMeansDbgNoIts - debug flag; if set, Lloyd's iteration is not |
|
|
//| performed, only initialization phase. |
|
|
//| Buf - special reusable structure which stores previously |
|
|
//| allocated memory, intended to avoid memory |
|
|
//| fragmentation when solving multiple subsequent |
|
|
//| problems: |
|
|
//| * MUST BE INITIALIZED WITH KMeansInitBuffers() |
|
|
//| CALL BEFORE FIRST PASS TO THIS FUNCTION! |
|
|
//| * subsequent passes must be made without |
|
|
//| re-initialization |
|
|
//| 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 |
|
|
//| IterationsCount - actual number of iterations performed by |
|
|
//| clusterizer |
|
|
//| CCol - array[0..NVars-1, 0..K-1]. matrix whose columns |
|
|
//| store cluster's centers |
|
|
//| NeedCCol - True in case caller requires to store result in |
|
|
//| CCol |
|
|
//| CRow - array[0..K-1, 0..NVars-1], same as CCol, but |
|
|
//| centers are stored in rows |
|
|
//| NeedCRow - True in case caller requires to store result in |
|
|
//| CCol |
|
|
//| XYC - array[NPoints], which contains cluster indexes |
|
|
//| Energy - merit function of clusterization |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::KMeansGenerateInternal(CMatrixDouble &xy,
|
|
int npoints,
|
|
int nvars,
|
|
int k,
|
|
int initalgo,
|
|
int seed,
|
|
int maxits,
|
|
int restarts,
|
|
bool kmeansdbgnoits,
|
|
int &info,
|
|
int &iterationscount,
|
|
CMatrixDouble &ccol,
|
|
bool needccol,
|
|
CMatrixDouble &crow,
|
|
bool needcrow,
|
|
CRowInt &xyc,
|
|
double &energy,
|
|
CKmeansBuffers &buf)
|
|
{
|
|
//--- create variables
|
|
int i1=0;
|
|
double e=0;
|
|
double eprev=0;
|
|
double v=0;
|
|
double vv=0;
|
|
bool waschanges;
|
|
bool zerosizeclusters;
|
|
int pass=0;
|
|
int itcnt=0;
|
|
CHighQualityRandState rs;
|
|
|
|
info=0;
|
|
iterationscount=0;
|
|
ccol.Resize(0,0);
|
|
crow.Resize(0,0);
|
|
xyc.Resize(0);
|
|
energy=0;
|
|
//--- Test parameters
|
|
if(npoints<k || nvars<1 || k<1 || restarts<1)
|
|
{
|
|
info=-1;
|
|
iterationscount=0;
|
|
return;
|
|
}
|
|
//--- TODO: special case K=1
|
|
//--- TODO: special case K=NPoints
|
|
info=1;
|
|
iterationscount=0;
|
|
//--- Multiple passes of k-means++ algorithm
|
|
if(seed<=0)
|
|
CHighQualityRand::HQRndRandomize(rs);
|
|
else
|
|
CHighQualityRand::HQRndSeed(325355,seed,rs);
|
|
xyc.Resize(npoints);
|
|
CApServ::RMatrixSetLengthAtLeast(buf.m_ctbest,k,nvars);
|
|
CApServ::IVectorSetLengthAtLeast(buf.m_xycbest,npoints);
|
|
buf.m_d2.Resize(npoints);
|
|
CApServ::IVectorSetLengthAtLeast(buf.m_csizes,k);
|
|
energy=CMath::m_maxrealnumber;
|
|
for(pass=1; pass<=restarts; pass++)
|
|
{
|
|
//--- Select initial centers.
|
|
//--- 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.
|
|
//--- Also note that SelectInitialCenters() may return degenerate set of centers
|
|
//--- (some of them have no corresponding points in dataset, some are non-distinct).
|
|
//--- Algorithm below is robust enough to deal with such set.
|
|
SelectInitialCenters(xy,npoints,nvars,initalgo,rs,k,buf.m_ct,buf.m_initbuf);
|
|
//--- Lloyd's iteration
|
|
if(!kmeansdbgnoits)
|
|
{
|
|
//--- Perform iteration as usual, in normal mode
|
|
xyc.Fill(-1,0,npoints);
|
|
eprev=CMath::m_maxrealnumber;
|
|
e=CMath::m_maxrealnumber;
|
|
itcnt=0;
|
|
while(maxits==0 || itcnt<maxits)
|
|
{
|
|
//--- Update iteration counter
|
|
itcnt++;
|
|
iterationscount++;
|
|
//--- Call KMeansUpdateDistances(), fill XYC with center numbers,
|
|
//--- D2 with center distances.
|
|
buf.m_xycprev=xyc;
|
|
buf.m_xycprev.Resize(npoints);
|
|
KMeansUpdateDistances(xy,0,npoints,nvars,buf.m_ct,0,k,xyc,buf.m_d2);
|
|
waschanges=false;
|
|
for(int i=0; i<npoints; i++)
|
|
waschanges=waschanges || xyc[i]!=buf.m_xycprev[i];
|
|
//--- Update centers
|
|
buf.m_csizes.Fill(0,0,k);
|
|
buf.m_ct=matrix<double>::Zeros(k,nvars);
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
buf.m_csizes.Add(xyc[i],1);
|
|
for(int i_=0; i_<nvars; i_++)
|
|
{
|
|
buf.m_ct.Add(xyc[i],i_,xy.Get(i,i_));
|
|
}
|
|
}
|
|
zerosizeclusters=false;
|
|
for(int j=0; j<k; j++)
|
|
{
|
|
if(buf.m_csizes[j]!=0)
|
|
{
|
|
v=(double)1/(double)buf.m_csizes[j];
|
|
buf.m_ct.Row(j,buf.m_ct.Row(j)*v);
|
|
}
|
|
zerosizeclusters=zerosizeclusters || buf.m_csizes[j]==0;
|
|
}
|
|
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, decrementing iteration counter
|
|
//--- in order to allow one more iteration (this one was useless
|
|
//--- and should not be counted).
|
|
if(!FixCenters(xy,npoints,nvars,buf.m_ct,k,buf.m_initbuf))
|
|
{
|
|
info=-3;
|
|
return;
|
|
}
|
|
itcnt--;
|
|
continue;
|
|
}
|
|
//--- Stop if one of two conditions is met:
|
|
//--- 1. nothing has changed during iteration
|
|
//--- 2. energy function increased after recalculation on new centers
|
|
e=0;
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
v=0.0;
|
|
i1=xyc[i];
|
|
for(int j=0; j<nvars; j++)
|
|
{
|
|
vv=xy.Get(i,j)-buf.m_ct.Get(i1,j);
|
|
v+=vv*vv;
|
|
}
|
|
e+=v;
|
|
}
|
|
if(!waschanges || e>=eprev)
|
|
break;
|
|
//--- Update EPrev
|
|
eprev=e;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//--- Debug mode: no Lloyd's iteration.
|
|
//--- We just calculate potential E.
|
|
KMeansUpdateDistances(xy,0,npoints,nvars,buf.m_ct,0,k,xyc,buf.m_d2);
|
|
e=buf.m_d2.Sum();
|
|
}
|
|
//--- Compare E with best centers found so far
|
|
if(e<energy)
|
|
{
|
|
//--- store partition.
|
|
energy=e;
|
|
CBlas::CopyMatrix(buf.m_ct,0,k-1,0,nvars-1,buf.m_ctbest,0,k-1,0,nvars-1);
|
|
buf.m_xycbest=xyc;
|
|
}
|
|
}
|
|
//--- Copy and transpose
|
|
if(needccol)
|
|
{
|
|
ccol.Resize(nvars,k);
|
|
CBlas::CopyAndTranspose(buf.m_ctbest,0,k-1,0,nvars-1,ccol,0,nvars-1,0,k-1);
|
|
}
|
|
if(needcrow)
|
|
{
|
|
crow.Resize(k,nvars);
|
|
CAblas::RMatrixCopy(k,nvars,buf.m_ctbest,0,0,crow,0,0);
|
|
}
|
|
xyc=buf.m_xycbest;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This procedure recalculates distances from points to centers and |
|
|
//| assigns each point to closest center. |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - dataset, array [0..NPoints-1, 0..NVars-1]. |
|
|
//| Idx0, Idx1 - define range of dataset [Idx0, Idx1) to process;|
|
|
//| right boundary is not included. |
|
|
//| NVars - number of variables, NVars >= 1 |
|
|
//| CT - matrix of centers, centers are stored in rows |
|
|
//| CIdx0, CIdx1 - define range of centers [CIdx0, CIdx1) to |
|
|
//| process; right boundary is not included. |
|
|
//| XYC - preallocated output buffer, |
|
|
//| XYDist2 - preallocated output buffer |
|
|
//| Tmp - temporary buffer, automatically reallocated if |
|
|
//| needed |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| XYC - new assignment of points to centers are stored |
|
|
//| in [Idx0, Idx1) |
|
|
//| XYDist2 - squared distances from points to their centers are |
|
|
//| stored in [Idx0, Idx1) |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::KMeansUpdateDistances(CMatrixDouble &xy,
|
|
int idx0,int idx1,int nvars,
|
|
CMatrixDouble &ct,int cidx0,
|
|
int cidx1,CRowInt &xyc,
|
|
CRowDouble &xydist2)
|
|
{
|
|
//--- create variables
|
|
int i0=0;
|
|
int i1=0;
|
|
int cclosest=0;
|
|
double dclosest=0;
|
|
double vv=0;
|
|
CApBuff buf;
|
|
double rcomplexity=0;
|
|
int task0=0;
|
|
int task1=0;
|
|
int pblkcnt=0;
|
|
int cblkcnt=0;
|
|
int vblkcnt=0;
|
|
int p0=0;
|
|
int p1=0;
|
|
int c0=0;
|
|
int c1=0;
|
|
int v0=0;
|
|
int v1=0;
|
|
double v00=0;
|
|
double v01=0;
|
|
double v10=0;
|
|
double v11=0;
|
|
double vp0=0;
|
|
double vp1=0;
|
|
double vc0=0;
|
|
double vc1=0;
|
|
int pcnt=0;
|
|
int pcntpadded=0;
|
|
int ccnt=0;
|
|
int ccntpadded=0;
|
|
int offs0=0;
|
|
int offs00=0;
|
|
int offs01=0;
|
|
int offs10=0;
|
|
int offs11=0;
|
|
int vcnt=0;
|
|
int stride=0;
|
|
//--- Quick exit for special cases
|
|
if(idx1<=idx0)
|
|
return;
|
|
if(cidx1<=cidx0)
|
|
return;
|
|
if(nvars<=0)
|
|
return;
|
|
//--- Dataset chunk is selected.
|
|
//--- Process it with blocked algorithm:
|
|
//--- * iterate over points, process them in KMeansBlockSize-ed chunks
|
|
//--- * for each chunk of dataset, iterate over centers, process them in KMeansBlockSize-ed chunks
|
|
//--- * for each chunk of dataset/centerset, iterate over variables, process them in KMeansBlockSize-ed chunks
|
|
if(!CAp::Assert(m_kmeansblocksize%2==0,__FUNCTION__": internal error"))
|
|
return;
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_ra0,m_kmeansblocksize*m_kmeansblocksize);
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_ra1,m_kmeansblocksize*m_kmeansblocksize);
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_ra2,m_kmeansblocksize*m_kmeansblocksize);
|
|
CApServ::RVectorSetLengthAtLeast(buf.m_ra3,m_kmeansblocksize);
|
|
CApServ::IVectorSetLengthAtLeast(buf.m_ia3,m_kmeansblocksize);
|
|
pblkcnt=CApServ::ChunksCount(idx1-idx0,m_kmeansblocksize);
|
|
cblkcnt=CApServ::ChunksCount(cidx1-cidx0,m_kmeansblocksize);
|
|
vblkcnt=CApServ::ChunksCount(nvars,m_kmeansblocksize);
|
|
for(int pblk=0; pblk<pblkcnt; pblk++)
|
|
{
|
|
//--- Process PBlk-th chunk of dataset.
|
|
p0=idx0+pblk*m_kmeansblocksize;
|
|
p1=MathMin(p0+m_kmeansblocksize,idx1);
|
|
//--- Prepare RA3[]/IA3[] for storage of best distances and best cluster numbers.
|
|
buf.m_ra3=vector<double>::Full(m_kmeansblocksize,CMath::m_maxrealnumber);
|
|
buf.m_ia3.Fill(-1,0,m_kmeansblocksize);
|
|
//--- Iterare over chunks of centerset.
|
|
for(int cblk=0; cblk<cblkcnt; cblk++)
|
|
{
|
|
//--- Process CBlk-th chunk of centerset
|
|
c0=cidx0+cblk*m_kmeansblocksize;
|
|
c1=MathMin(c0+m_kmeansblocksize,cidx1);
|
|
//--- At this point we have to calculate a set of pairwise distances
|
|
//--- between points [P0,P1) and centers [C0,C1) and select best center
|
|
//--- for each point. It can also be done with blocked algorithm
|
|
//--- (blocking for variables).
|
|
//--- Following arrays are used:
|
|
//--- * RA0[] - matrix of distances, padded by zeros for even size,
|
|
//--- rows are stored with stride KMeansBlockSize.
|
|
//--- * RA1[] - matrix of points (variables corresponding to current
|
|
//--- block are extracted), padded by zeros for even size,
|
|
//--- rows are stored with stride KMeansBlockSize.
|
|
//--- * RA2[] - matrix of centers (variables corresponding to current
|
|
//--- block are extracted), padded by zeros for even size,
|
|
//--- rows are stored with stride KMeansBlockSize.
|
|
pcnt=p1-p0;
|
|
pcntpadded=pcnt+pcnt%2;
|
|
ccnt=c1-c0;
|
|
ccntpadded=ccnt+ccnt%2;
|
|
stride=m_kmeansblocksize;
|
|
if(!CAp::Assert(pcntpadded<=m_kmeansblocksize,__FUNCTION__": integrity error"))
|
|
return;
|
|
if(!CAp::Assert(ccntpadded<=m_kmeansblocksize,__FUNCTION__": integrity error"))
|
|
return;
|
|
for(int i=0; i<pcntpadded; i++)
|
|
for(int j=0; j<ccntpadded; j++)
|
|
buf.m_ra0.Set(i*stride+j,0.0);
|
|
for(int vblk=0; vblk<vblkcnt; vblk++)
|
|
{
|
|
//--- Fetch VBlk-th block of variables to arrays RA1 (points) and RA2 (centers).
|
|
//--- Pad points and centers with zeros.
|
|
v0=vblk*m_kmeansblocksize;
|
|
v1=MathMin(v0+m_kmeansblocksize,nvars);
|
|
vcnt=v1-v0;
|
|
for(int i=0; i<pcnt; i++)
|
|
for(int j=0; j<vcnt; j++)
|
|
buf.m_ra1.Set(i*stride+j,xy.Get(p0+i,v0+j));
|
|
for(int i=pcnt; i<pcntpadded; i++)
|
|
for(int j=0; j<vcnt; j++)
|
|
buf.m_ra1.Set(i*stride+j,0.0);
|
|
for(int i=0; i<ccnt; i++)
|
|
for(int j=0; j<vcnt; j++)
|
|
buf.m_ra2.Set(i*stride+j,ct.Get(c0+i,v0+j));
|
|
for(int i=ccnt; i<ccntpadded; i++)
|
|
for(int j=0; j<vcnt; j++)
|
|
buf.m_ra2.Set(i*stride+j,0.0);
|
|
//--- Update distance matrix with sums-of-squared-differences of RA1 and RA2
|
|
i0=0;
|
|
while(i0<pcntpadded)
|
|
{
|
|
i1=0;
|
|
while(i1<ccntpadded)
|
|
{
|
|
offs0=i0*stride+i1;
|
|
v00=buf.m_ra0[offs0];
|
|
v01=buf.m_ra0[offs0+1];
|
|
v10=buf.m_ra0[offs0+stride];
|
|
v11=buf.m_ra0[offs0+stride+1];
|
|
offs00=i0*stride;
|
|
offs01=offs00+stride;
|
|
offs10=i1*stride;
|
|
offs11=offs10+stride;
|
|
for(int j=0; j<vcnt; j++)
|
|
{
|
|
vp0=buf.m_ra1[offs00+j];
|
|
vp1=buf.m_ra1[offs01+j];
|
|
vc0=buf.m_ra2[offs10+j];
|
|
vc1=buf.m_ra2[offs11+j];
|
|
vv=vp0-vc0;
|
|
v00+=vv*vv;
|
|
vv=vp0-vc1;
|
|
v01+=vv*vv;
|
|
vv=vp1-vc0;
|
|
v10+=vv*vv;
|
|
vv=vp1-vc1;
|
|
v11+=vv*vv;
|
|
}
|
|
offs0=i0*stride+i1;
|
|
buf.m_ra0.Set(offs0,v00);
|
|
buf.m_ra0.Set(offs0+1,v01);
|
|
buf.m_ra0.Set(offs0+stride,v10);
|
|
buf.m_ra0.Set(offs0+stride+1,v11);
|
|
i1+=2;
|
|
}
|
|
i0+=2;
|
|
}
|
|
}
|
|
for(int i=0; i<pcnt; i++)
|
|
{
|
|
cclosest=buf.m_ia3[i];
|
|
dclosest=buf.m_ra3[i];
|
|
for(int j=0; j<ccnt; j++)
|
|
if(buf.m_ra0[i*stride+j]<dclosest)
|
|
{
|
|
dclosest=buf.m_ra0[i*stride+j];
|
|
cclosest=c0+j;
|
|
}
|
|
buf.m_ia3.Set(i,cclosest);
|
|
buf.m_ra3.Set(i,dclosest);
|
|
}
|
|
}
|
|
//--- Store best centers to XYC[]
|
|
for(int i=p0; i<p1; i++)
|
|
{
|
|
xyc.Set(i,buf.m_ia3[i-p0]);
|
|
xydist2.Set(i,buf.m_ra3[i-p0]);
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function selects initial centers according to specified |
|
|
//| initialization algorithm. |
|
|
//| IMPORTANT: this function provides no guarantees regarding |
|
|
//| selection of DIFFERENT centers. Centers returned by |
|
|
//| this function may include duplicates(say, when random |
|
|
//| sampling is used). It is also possible that some |
|
|
//| centers are empty. Algorithm which uses this function |
|
|
//| must be able to deal with it. Say, you may want to use|
|
|
//| FixCenters() in order to fix empty centers. |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - dataset, array [0..NPoints-1, 0..NVars-1]. |
|
|
//| NPoints - points count |
|
|
//| NVars - number of variables, NVars >= 1 |
|
|
//| InitAlgo - initialization algorithm: |
|
|
//| * 0 - automatic selection of best algorithm |
|
|
//| * 1 - random selection |
|
|
//| * 2 - k-means++ |
|
|
//| * 3 - fast-greedy init |
|
|
//| * -1 - first K rows of dataset are used (debug |
|
|
//| algorithm) |
|
|
//| RS - RNG used to select centers |
|
|
//| K - number of centers, K >= 1 |
|
|
//| CT - possibly preallocated output buffer, resized if |
|
|
//| needed |
|
|
//| InitBuf - internal buffer, possibly unitialized instance of |
|
|
//| APBuffers. It is recommended to use this instance |
|
|
//| only with SelectInitialCenters() and FixCenters() |
|
|
//| functions, because these functions may allocate |
|
|
//| really large storage. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| CT - set of K clusters, one per row |
|
|
//| RESULT: True on success, False on failure(impossible to create K |
|
|
//| independent clusters) |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::SelectInitialCenters(CMatrixDouble &xy,int npoints,
|
|
int nvars,int initalgo,CHighQualityRandState &rs,
|
|
int k,CMatrixDouble &ct,CApBuff &initbuf)
|
|
{
|
|
//--- create variables
|
|
int cidx=0;
|
|
int i=0;
|
|
int j=0;
|
|
double v=0;
|
|
double vv=0;
|
|
double s=0;
|
|
int lastnz=0;
|
|
int ptidx=0;
|
|
int samplesize=0;
|
|
int samplescntnew=0;
|
|
int samplescntall=0;
|
|
double samplescale=0;
|
|
int i_=0;
|
|
//--- Check parameters
|
|
if(!CAp::Assert(npoints>0,__FUNCTION__": internal error"))
|
|
return;
|
|
if(!CAp::Assert(nvars>0,__FUNCTION__": internal error"))
|
|
return;
|
|
if(!CAp::Assert(k>0,__FUNCTION__": internal error"))
|
|
return;
|
|
if(initalgo==0)
|
|
initalgo=3;
|
|
CApServ::RMatrixSetLengthAtLeast(ct,k,nvars);
|
|
//--- Random initialization
|
|
if(initalgo==-1)
|
|
{
|
|
for(i=0; i<k; i++)
|
|
ct.Row(i,xy[i%npoints]+0);
|
|
return;
|
|
}
|
|
//--- Random initialization
|
|
if(initalgo==1)
|
|
{
|
|
for(i=0; i<k; i++)
|
|
{
|
|
j=CHighQualityRand::HQRndUniformI(rs,npoints);
|
|
ct.Row(i,xy[j]+0);
|
|
}
|
|
return;
|
|
}
|
|
//--- k-means++ initialization
|
|
if(initalgo==2)
|
|
{
|
|
//--- Prepare distances array.
|
|
//--- Select initial center at random.
|
|
initbuf.m_ra0=vector<double>::Full(npoints,CMath::m_maxrealnumber);
|
|
ptidx=CHighQualityRand::HQRndUniformI(rs,npoints);
|
|
ct.Row(0,xy[ptidx]+0);
|
|
//--- For each newly added center repeat:
|
|
//--- * reevaluate distances from points to best centers
|
|
//--- * sample points with probability dependent on distance
|
|
//--- * add new center
|
|
for(cidx=0; cidx<k-1; cidx++)
|
|
{
|
|
//--- Reevaluate distances
|
|
s=0.0;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
v=0.0;
|
|
for(j=0; j<=nvars-1; j++)
|
|
{
|
|
vv=xy.Get(i,j)-ct.Get(cidx,j);
|
|
v+=vv*vv;
|
|
}
|
|
if(v<initbuf.m_ra0[i])
|
|
initbuf.m_ra0.Set(i,v);
|
|
s+=initbuf.m_ra0[i];
|
|
}
|
|
//
|
|
//--- If all distances are zero, it means that we can not find enough
|
|
//--- distinct points. In this case we just select non-distinct center
|
|
//--- at random and continue iterations. This issue will be handled
|
|
//--- later in the FixCenters() function.
|
|
//
|
|
if(s==0.0)
|
|
{
|
|
ptidx=CHighQualityRand::HQRndUniformI(rs,npoints);
|
|
ct.Row(cidx+1,xy[ptidx]+0);
|
|
continue;
|
|
}
|
|
//--- Select point as center using its distance.
|
|
//--- We also handle situation when because of rounding errors
|
|
//--- no point was selected - in this case, last non-zero one
|
|
//--- will be used.
|
|
v=CHighQualityRand::HQRndUniformR(rs);
|
|
vv=0.0;
|
|
lastnz=-1;
|
|
ptidx=-1;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
if(initbuf.m_ra0[i]==0.0)
|
|
continue;
|
|
lastnz=i;
|
|
vv+=initbuf.m_ra0[i];
|
|
if(v<=vv/s)
|
|
{
|
|
ptidx=i;
|
|
break;
|
|
}
|
|
}
|
|
if(!CAp::Assert(lastnz>=0,__FUNCTION__": integrity error"))
|
|
return;
|
|
if(ptidx<0)
|
|
ptidx=lastnz;
|
|
ct.Row(cidx+1,xy[ptidx]+0);
|
|
}
|
|
return;
|
|
}
|
|
//--- "Fast-greedy" algorithm based on "Scalable k-means++".
|
|
//--- We perform several rounds, within each round we sample about 0.5*K points
|
|
//--- (not exactly 0.5*K) until we have 2*K points sampled. Before each round
|
|
//--- we calculate distances from dataset points to closest points sampled so far.
|
|
//--- We sample dataset points independently using distance xtimes 0.5*K divided by total
|
|
//--- as probability (similar to k-means++, but each point is sampled independently;
|
|
//--- after each round we have roughtly 0.5*K points added to sample).
|
|
//--- After sampling is done, we run "greedy" version of k-means++ on this subsample
|
|
//--- which selects most distant point on every round.
|
|
if(initalgo==3)
|
|
{
|
|
//--- Prepare arrays.
|
|
//--- Select initial center at random, add it to "new" part of sample,
|
|
//--- which is stored at the beginning of the array
|
|
samplesize=2*k;
|
|
samplescale=0.5*k;
|
|
CApServ::RMatrixSetLengthAtLeast(initbuf.m_rm0,samplesize,nvars);
|
|
ptidx=CHighQualityRand::HQRndUniformI(rs,npoints);
|
|
initbuf.m_rm0.Row(0,xy[ptidx]+0);
|
|
samplescntnew=1;
|
|
samplescntall=1;
|
|
initbuf.m_ra1=vector<double>::Zeros(npoints);
|
|
CApServ::IVectorSetLengthAtLeast(initbuf.m_ia1,npoints);
|
|
initbuf.m_ra0=vector<double>::Full(npoints,CMath::m_maxrealnumber);
|
|
//--- Repeat until samples count is 2*K
|
|
while(samplescntall<samplesize)
|
|
{
|
|
//--- Evaluate distances from points to NEW centers, store to RA1.
|
|
//--- Reset counter of "new" centers.
|
|
KMeansUpdateDistances(xy,0,npoints,nvars,initbuf.m_rm0,samplescntall-samplescntnew,samplescntall,initbuf.m_ia1,initbuf.m_ra1);
|
|
samplescntnew=0;
|
|
//--- Merge new distances with old ones.
|
|
//--- Calculate sum of distances, if sum is exactly zero - fill sample
|
|
//--- by randomly selected points and terminate.
|
|
s=0.0;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
initbuf.m_ra0.Set(i,MathMin(initbuf.m_ra0[i],initbuf.m_ra1[i]));
|
|
s+=initbuf.m_ra0[i];
|
|
}
|
|
if(s==0.0)
|
|
{
|
|
while(samplescntall<samplesize)
|
|
{
|
|
ptidx=CHighQualityRand::HQRndUniformI(rs,npoints);
|
|
initbuf.m_rm0.Row(samplescntall,xy[ptidx]+0);
|
|
samplescntall++;
|
|
samplescntnew++;
|
|
}
|
|
break;
|
|
}
|
|
//--- Sample points independently.
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
if(samplescntall==samplesize)
|
|
break;
|
|
if(initbuf.m_ra0[i]==0.0)
|
|
continue;
|
|
if(CHighQualityRand::HQRndUniformR(rs)<=(samplescale*initbuf.m_ra0[i]/s))
|
|
{
|
|
initbuf.m_rm0.Row(samplescntall,xy[i]+0);
|
|
samplescntall++;
|
|
samplescntnew++;
|
|
}
|
|
}
|
|
}
|
|
//--- Run greedy version of k-means on sampled points
|
|
|
|
initbuf.m_ra0=vector<double>::Full(samplescntall,CMath::m_maxrealnumber);
|
|
ptidx=CHighQualityRand::HQRndUniformI(rs,samplescntall);
|
|
ct.Row(0,initbuf.m_rm0[ptidx]+0);
|
|
for(cidx=0; cidx<k-1; cidx++)
|
|
{
|
|
//--- Reevaluate distances
|
|
for(i=0; i<samplescntall; i++)
|
|
{
|
|
v=0.0;
|
|
for(j=0; j<nvars; j++)
|
|
{
|
|
vv=initbuf.m_rm0.Get(i,j)-ct.Get(cidx,j);
|
|
v+=vv*vv;
|
|
}
|
|
if(v<initbuf.m_ra0[i])
|
|
initbuf.m_ra0.Set(i,v);
|
|
}
|
|
//--- Select point as center in greedy manner - most distant
|
|
//--- point is selected.
|
|
ptidx=0;
|
|
for(i=0; i<samplescntall; i++)
|
|
{
|
|
if(initbuf.m_ra0[i]>initbuf.m_ra0[ptidx])
|
|
ptidx=i;
|
|
}
|
|
ct.Row(cidx+1,initbuf.m_rm0[ptidx]+0);
|
|
}
|
|
return;
|
|
}
|
|
//--- Internal error
|
|
CAp::Assert(false,__FUNCTION__": internal error");
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function "fixes" centers, i.e. replaces ones which have no |
|
|
//| neighbor points by new centers which have at least one neighbor. |
|
|
//| If it is impossible to fix centers(not enough distinct points in |
|
|
//| the dataset), this function returns False. |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - dataset, array [0..NPoints-1, 0..NVars-1]. |
|
|
//| NPoints - points count, >= 1 |
|
|
//| NVars - number of variables, NVars >= 1 |
|
|
//| CT - centers |
|
|
//| K - number of centers, K >= 1 |
|
|
//| InitBuf - internal buffer, possibly unitialized instance of |
|
|
//| APBuffers. It is recommended to use this instance |
|
|
//| only with SelectInitialCenters() and FixCenters() |
|
|
//| functions, because these functions may allocate |
|
|
//| really large storage. |
|
|
//| UpdatePool - shared pool seeded with instance of APBuffers |
|
|
//| structure (seed instance can be unitialized). Used |
|
|
//| internally with KMeansUpdateDistances() function. |
|
|
//| It is recommended to use this pool ONLY with |
|
|
//| KMeansUpdateDistances() function. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| CT - set of K centers, one per row |
|
|
//| RESULT: True on success, False on failure(impossible to create K |
|
|
//| independent clusters) |
|
|
//+------------------------------------------------------------------+
|
|
bool CClustering::FixCenters(CMatrixDouble &xy,
|
|
int npoints,
|
|
int nvars,
|
|
CMatrixDouble &ct,
|
|
int k,
|
|
CApBuff &initbuf)
|
|
{
|
|
//--- create variables
|
|
int fixiteration=0;
|
|
int centertofix=0;
|
|
int pdistant=0;
|
|
double ddistant=0;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(npoints>=1,__FUNCTION__": internal error"))
|
|
return(false);
|
|
if(!CAp::Assert(nvars>=1,__FUNCTION__": internal error"))
|
|
return(false);
|
|
if(!CAp::Assert(k>=1,__FUNCTION__": internal error"))
|
|
return(false);
|
|
//--- Calculate distances from points to best centers (RA0)
|
|
//--- and best center indexes (IA0)
|
|
CApServ::IVectorSetLengthAtLeast(initbuf.m_ia0,npoints);
|
|
CApServ::RVectorSetLengthAtLeast(initbuf.m_ra0,npoints);
|
|
KMeansUpdateDistances(xy,0,npoints,nvars,ct,0,k,initbuf.m_ia0,initbuf.m_ra0);
|
|
//--- Repeat loop:
|
|
//--- * find first center which has no corresponding point
|
|
//--- * set it to the most distant (from the rest of the centerset) point
|
|
//--- * recalculate distances, update IA0/RA0
|
|
//--- * repeat
|
|
//--- Loop is repeated for at most 2*K iterations. It is stopped once we have
|
|
//--- no "empty" clusters.
|
|
CApServ::BVectorSetLengthAtLeast(initbuf.m_ba,k);
|
|
for(fixiteration=0; fixiteration<=2*k; fixiteration++)
|
|
{
|
|
//--- Select center to fix (one which is not mentioned in IA0),
|
|
//--- terminate if there is no such center.
|
|
//--- BA0[] stores True for centers which have at least one point.
|
|
ArrayFill(initbuf.m_ba,0,k,false);
|
|
for(int i=0; i<npoints; i++)
|
|
initbuf.m_ba[initbuf.m_ia0[i]]=true;
|
|
centertofix=-1;
|
|
for(int i=0; i<k; i++)
|
|
if(!initbuf.m_ba[i])
|
|
{
|
|
centertofix=i;
|
|
break;
|
|
}
|
|
if(centertofix<0)
|
|
return(true);
|
|
//--- Replace center to fix by the most distant point.
|
|
//--- Update IA0/RA0
|
|
pdistant=0;
|
|
ddistant=initbuf.m_ra0[pdistant];
|
|
for(int i=0; i<npoints; i++)
|
|
if(initbuf.m_ra0[i]>ddistant)
|
|
{
|
|
ddistant=initbuf.m_ra0[i];
|
|
pdistant=i;
|
|
}
|
|
if(ddistant==0.0)
|
|
break;
|
|
ct.Row(centertofix,xy[pdistant]+0);
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
v=0.0;
|
|
for(int j=0; j<nvars; j++)
|
|
v=v+CMath::Sqr(xy.Get(i,j)-ct.Get(centertofix,j));
|
|
if(v<initbuf.m_ra0[i])
|
|
{
|
|
initbuf.m_ra0.Set(i,v);
|
|
initbuf.m_ia0.Set(i,centertofix);
|
|
}
|
|
}
|
|
}
|
|
//--- return result
|
|
return(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs agglomerative hierarchical clustering |
|
|
//| using precomputed distance matrix. Internal function, should not |
|
|
//| be called directly. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - clusterizer state, initialized by |
|
|
//| ClusterizerCreate() |
|
|
//| D - distance matrix, array[S.m_nfeatures,S.m_nfeatures]|
|
|
//| Contents of the matrix is destroyed during |
|
|
//| algorithm operation. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Rep - clustering results; see description of AHCReport |
|
|
//| structure for more information. |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::ClusterizerRunAHCInternal(CClusterizerState &s,
|
|
CMatrixDouble &d,
|
|
CAHCReport &rep)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
double v=0;
|
|
int mergeidx=0;
|
|
int c0=0;
|
|
int c1=0;
|
|
int s0=0;
|
|
int s1=0;
|
|
int ar=0;
|
|
int br=0;
|
|
CRowInt cidx;
|
|
CRowInt csizes;
|
|
CRowInt nnidx;
|
|
CMatrixInt cinfo;
|
|
int n0=0;
|
|
int n1=0;
|
|
int ni=0;
|
|
double d01=0;
|
|
int npoints=s.m_NPoints;
|
|
//--- Fill Rep.NPoints, quick exit when NPoints<=1
|
|
rep.m_NPoints=npoints;
|
|
//--- Quick exit
|
|
if(npoints==0)
|
|
{
|
|
rep.m_p.Resize(0);
|
|
rep.m_z.Resize(0,0);
|
|
rep.m_pz.Resize(0,0);
|
|
rep.m_pm.Resize(0,0);
|
|
rep.m_mergedist.Resize(0);
|
|
rep.m_terminationtype=1;
|
|
return;
|
|
}
|
|
if(npoints==1)
|
|
{
|
|
rep.m_p.Resize(1);
|
|
rep.m_z.Resize(0,0);
|
|
rep.m_pz.Resize(0,0);
|
|
rep.m_pm.Resize(0,0);
|
|
rep.m_mergedist.Resize(0);
|
|
rep.m_p.Set(0,0);
|
|
rep.m_terminationtype=1;
|
|
return;
|
|
}
|
|
//--- Allocate
|
|
rep.m_z.Resize(npoints-1,2);
|
|
rep.m_mergedist.Resize(npoints-1);
|
|
rep.m_terminationtype=1;
|
|
//--- Build list of nearest neighbors
|
|
nnidx.Resize(npoints);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
//--- Calculate index of the nearest neighbor
|
|
k=-1;
|
|
v=CMath::m_maxrealnumber;
|
|
for(j=0; j<npoints; j++)
|
|
if(j!=i && d.Get(i,j)<v)
|
|
{
|
|
k=j;
|
|
v=d.Get(i,j);
|
|
}
|
|
if(!CAp::Assert(v<CMath::m_maxrealnumber,__FUNCTION__": internal error"))
|
|
return;
|
|
nnidx.Set(i,k);
|
|
}
|
|
//--- For AHCAlgo=4 (Ward's method) replace distances by their squares times 0.5
|
|
if(s.m_ahcalgo==4)
|
|
{
|
|
d.Resize(npoints,npoints);
|
|
d=MathPow(d.ToMatrix()+0,2.0)*0.5;
|
|
}
|
|
//--- Distance matrix is built, perform merges.
|
|
//--- NOTE 1: CIdx is array[NPoints] which maps rows/columns of the
|
|
//--- distance matrix D to indexes of clusters. Values of CIdx
|
|
//--- from [0,NPoints) denote single-point clusters, and values
|
|
//--- from [NPoints,2*NPoints-1) denote ones obtained by merging
|
|
//--- smaller clusters. Negative calues correspond to absent clusters.
|
|
//--- Initially it contains [0...NPoints-1], after each merge
|
|
//--- one element of CIdx (one with index C0) is replaced by
|
|
//--- NPoints+MergeIdx, and another one with index C1 is
|
|
//--- rewritten by -1.
|
|
//--- NOTE 2: CSizes is array[NPoints] which stores sizes of clusters.
|
|
cidx.Resize(npoints);
|
|
csizes.Resize(npoints);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
cidx.Set(i,i);
|
|
csizes.Set(i,1);
|
|
}
|
|
for(mergeidx=0; mergeidx<npoints-1; mergeidx++)
|
|
{
|
|
//--- Select pair of clusters (C0,C1) with CIdx[C0]<CIdx[C1] to merge.
|
|
c0=-1;
|
|
c1=-1;
|
|
d01=CMath::m_maxrealnumber;
|
|
for(i=0; i<npoints; i++)
|
|
if(cidx[i]>=0)
|
|
{
|
|
if(d.Get(i,nnidx[i])<d01)
|
|
{
|
|
c0=i;
|
|
c1=nnidx[i];
|
|
d01=d.Get(i,nnidx[i]);
|
|
}
|
|
}
|
|
if(!CAp::Assert(d01<CMath::m_maxrealnumber,__FUNCTION__": internal error"))
|
|
return;
|
|
if(cidx[c0]>cidx[c1])
|
|
{
|
|
i=c1;
|
|
c1=c0;
|
|
c0=i;
|
|
}
|
|
//--- Fill one row of Rep.Z and one element of Rep.MergeDist
|
|
rep.m_z.Set(mergeidx,0,cidx[c0]);
|
|
rep.m_z.Set(mergeidx,1,cidx[c1]);
|
|
rep.m_mergedist.Set(mergeidx,d01);
|
|
//--- Update distance matrix:
|
|
//--- * row/column C0 are updated by distances to the new cluster
|
|
//--- * row/column C1 are considered empty (we can fill them by zeros,
|
|
//--- but do not want to spend time - we just ignore them)
|
|
//--- NOTE: it is important to update distance matrix BEFORE CIdx/CSizes
|
|
//--- are updated.
|
|
if(!CAp::Assert(s.m_ahcalgo==0 || s.m_ahcalgo==1 || s.m_ahcalgo==2 || s.m_ahcalgo==3 || s.m_ahcalgo==4,
|
|
__FUNCTION__": internal error"))
|
|
return;
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
if(i!=c0 && i!=c1)
|
|
{
|
|
n0=csizes[c0];
|
|
n1=csizes[c1];
|
|
ni=csizes[i];
|
|
switch(s.m_ahcalgo)
|
|
{
|
|
case 0:
|
|
d.Set(i,c0,MathMax(d.Get(i,c0),d.Get(i,c1)));
|
|
break;
|
|
case 1:
|
|
d.Set(i,c0,MathMin(d.Get(i,c0),d.Get(i,c1)));
|
|
break;
|
|
case 2:
|
|
d.Set(i,c0,(csizes[c0]*d.Get(i,c0)+csizes[c1]*d.Get(i,c1))/(csizes[c0]+csizes[c1]));
|
|
break;
|
|
case 3:
|
|
d.Set(i,c0,(d.Get(i,c0)+d.Get(i,c1))/2);
|
|
break;
|
|
case 4:
|
|
d.Set(i,c0,((n0+ni)*d.Get(i,c0)+(n1+ni)*d.Get(i,c1)-ni*d01)/(n0+n1+ni));
|
|
break;
|
|
}
|
|
d.Set(c0,i,d.Get(i,c0));
|
|
}
|
|
}
|
|
//--- Update CIdx and CSizes
|
|
cidx.Set(c0,npoints+mergeidx);
|
|
cidx.Set(c1,-1);
|
|
csizes.Set(c0,csizes[c0]+csizes[c1]);
|
|
csizes.Set(c1,0);
|
|
//--- Update nearest neighbors array:
|
|
//--- * update nearest neighbors of everything except for C0/C1
|
|
//--- * update neighbors of C0/C1
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
if(cidx[i]>=0 && i!=c0 && (nnidx[i]==c0 || nnidx[i]==c1))
|
|
{
|
|
//--- I-th cluster which is distinct from C0/C1 has former C0/C1 cluster as its nearest
|
|
//--- neighbor. We handle this issue depending on specific AHC algorithm being used.
|
|
if(s.m_ahcalgo==1)
|
|
{
|
|
//--- Single linkage. Merging of two clusters together
|
|
//--- does NOT change distances between new cluster and
|
|
//--- other clusters.
|
|
//
|
|
//--- The only thing we have to do is to update nearest neighbor index
|
|
nnidx.Set(i,c0);
|
|
}
|
|
else
|
|
{
|
|
//--- Something other than single linkage. We have to re-examine
|
|
//--- all the row to find nearest neighbor.
|
|
k=-1;
|
|
v=CMath::m_maxrealnumber;
|
|
for(j=0; j<npoints; j++)
|
|
{
|
|
if(cidx[j]>=0 && j!=i && d.Get(i,j)<v)
|
|
{
|
|
k=j;
|
|
v=d.Get(i,j);
|
|
}
|
|
}
|
|
if(!CAp::Assert(v<CMath::m_maxrealnumber || mergeidx==npoints-2,__FUNCTION__": internal error"))
|
|
return;
|
|
nnidx.Set(i,k);
|
|
}
|
|
}
|
|
}
|
|
k=-1;
|
|
v=CMath::m_maxrealnumber;
|
|
for(j=0; j<npoints; j++)
|
|
if(cidx[j]>=0 && j!=c0 && d.Get(c0,j)<v)
|
|
{
|
|
k=j;
|
|
v=d.Get(c0,j);
|
|
}
|
|
if(!CAp::Assert(v<CMath::m_maxrealnumber || mergeidx==npoints-2,__FUNCTION__": internal error"))
|
|
return;
|
|
nnidx.Set(c0,k);
|
|
}
|
|
//--- Calculate Rep.P and Rep.PM.
|
|
//--- In order to do that, we fill CInfo matrix - (2*NPoints-1)*3 matrix,
|
|
//--- with I-th row containing:
|
|
//--- * CInfo[I,0] - size of I-th cluster
|
|
//--- * CInfo[I,1] - beginning of I-th cluster
|
|
//--- * CInfo[I,2] - end of I-th cluster
|
|
//--- * CInfo[I,3] - height of I-th cluster
|
|
//--- We perform it as follows:
|
|
//--- * first NPoints clusters have unit size (CInfo[I,0]=1) and zero
|
|
//--- height (CInfo[I,3]=0)
|
|
//--- * we replay NPoints-1 merges from first to last and fill sizes of
|
|
//--- corresponding clusters (new size is a sum of sizes of clusters
|
|
//--- being merged) and height (new height is max(heights)+1).
|
|
//--- * now we ready to determine locations of clusters. Last cluster
|
|
//--- spans entire dataset, we know it. We replay merges from last to
|
|
//--- first, during each merge we already know location of the merge
|
|
//--- result, and we can position first cluster to the left part of
|
|
//--- the result, and second cluster to the right part.
|
|
rep.m_p.Resize(npoints);
|
|
rep.m_pm.Resize(npoints-1,6);
|
|
cinfo.Resize(2*npoints-1,4);
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
cinfo.Set(i,0,1);
|
|
cinfo.Set(i,3,0);
|
|
}
|
|
for(i=0; i<npoints-1; i++)
|
|
{
|
|
cinfo.Set(npoints+i,0,cinfo.Get(rep.m_z.Get(i,0),0)+cinfo.Get(rep.m_z.Get(i,1),0));
|
|
cinfo.Set(npoints+i,3,MathMax(cinfo.Get(rep.m_z.Get(i,0),3),cinfo.Get(rep.m_z.Get(i,1),3))+1);
|
|
}
|
|
cinfo.Set(2*npoints-2,1,0);
|
|
cinfo.Set(2*npoints-2,2,npoints-1);
|
|
for(i=npoints-2; i>=0; i--)
|
|
{
|
|
//--- We merge C0 which spans [A0,B0] and C1 (spans [A1,B1]),
|
|
//--- with unknown A0, B0, A1, B1. However, we know that result
|
|
//--- is CR, which spans [AR,BR] with known AR/BR, and we know
|
|
//--- sizes of C0, C1, CR (denotes as S0, S1, SR).
|
|
c0=rep.m_z.Get(i,0);
|
|
c1=rep.m_z.Get(i,1);
|
|
s0=cinfo.Get(c0,0);
|
|
s1=cinfo.Get(c1,0);
|
|
ar=cinfo.Get(npoints+i,1);
|
|
br=cinfo.Get(npoints+i,2);
|
|
cinfo.Set(c0,1,ar);
|
|
cinfo.Set(c0,2,ar+s0-1);
|
|
cinfo.Set(c1,1,br-(s1-1));
|
|
cinfo.Set(c1,2,br);
|
|
rep.m_pm.Set(i,0,cinfo.Get(c0,1));
|
|
rep.m_pm.Set(i,1,cinfo.Get(c0,2));
|
|
rep.m_pm.Set(i,2,cinfo.Get(c1,1));
|
|
rep.m_pm.Set(i,3,cinfo.Get(c1,2));
|
|
rep.m_pm.Set(i,4,cinfo.Get(c0,3));
|
|
rep.m_pm.Set(i,5,cinfo.Get(c1,3));
|
|
}
|
|
for(i=0; i<npoints; i++)
|
|
{
|
|
if(!CAp::Assert(cinfo.Get(i,1)==cinfo.Get(i,2)))
|
|
return;
|
|
rep.m_p.Set(i,cinfo.Get(i,1));
|
|
}
|
|
//--- Calculate Rep.PZ
|
|
rep.m_pz.Resize(npoints-1,2);
|
|
for(i=0; i<npoints-1; i++)
|
|
{
|
|
rep.m_pz.Set(i,0,rep.m_z.Get(i,0));
|
|
rep.m_pz.Set(i,1,rep.m_z.Get(i,1));
|
|
if(rep.m_pz.Get(i,0)<npoints)
|
|
rep.m_pz.Set(i,0,rep.m_p[rep.m_pz.Get(i,0)]);
|
|
if(rep.m_pz.Get(i,1)<npoints)
|
|
rep.m_pz.Set(i,1,rep.m_p[rep.m_pz.Get(i,1)]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function recursively evaluates distance matrix for |
|
|
//| SOME(not all!) distance types. |
|
|
//| INPUT PARAMETERS: |
|
|
//| XY - array[?, NFeatures], dataset |
|
|
//| NFeatures - number of features, >= 1 |
|
|
//| DistType - distance function: |
|
|
//| * 0 Chebyshev distance(L - inf norm) |
|
|
//| * 1 city block distance(L1 norm) |
|
|
//| D - preallocated output matrix |
|
|
//| I0, I1 - half interval of rows to calculate: [I0, I1) is |
|
|
//| processed |
|
|
//| J0, J1 - half interval of cols to calculate: [J0, J1) is |
|
|
//| processed |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| D - array[NPoints, NPoints], distance matrix upper |
|
|
//| triangle and main diagonal are initialized with |
|
|
//| data. |
|
|
//| NOTE: intersection of [I0, I1) and [J0, J1) may completely lie |
|
|
//| in upper triangle, only partially intersect with it, or |
|
|
//| have zero intersection. In any case, only intersection of |
|
|
//| submatrix given by [I0, I1)*[J0, J1) with upper triangle |
|
|
//| of the matrix is evaluated. |
|
|
//| Say, for 4x4 distance matrix A: |
|
|
//| * [0, 2)*[0, 2) will result in evaluation of A00, A01, A11 |
|
|
//| * [2, 4)*[2, 4) will result in evaluation of A22, A23, A32, A33|
|
|
//| * [2, 4)*[0, 2) will result in evaluation of empty set of |
|
|
//| elements |
|
|
//+------------------------------------------------------------------+
|
|
void CClustering::EvaluateDistanceMatrixRec(CMatrixDouble &xy,
|
|
int nfeatures,
|
|
int disttype,
|
|
CMatrixDouble &d,
|
|
int i0,
|
|
int i1,
|
|
int j0,
|
|
int j1)
|
|
{
|
|
//--- create variables
|
|
int len0=0;
|
|
int len1=0;
|
|
double v=0;
|
|
double vv=0;
|
|
//--- check
|
|
if(!CAp::Assert(disttype==0 || disttype==1,__FUNCTION__": incorrect DistType"))
|
|
return;
|
|
//--- Normalize J0/J1:
|
|
//--- * J0:=max(J0,I0) - we ignore lower triangle
|
|
//--- * J1:=max(J1,J0) - normalize J1
|
|
j0=MathMax(j0,i0);
|
|
j1=MathMax(j1,j0);
|
|
if(j1<=j0 || i1<=i0)
|
|
return;
|
|
//--- Sequential processing
|
|
for(int i=i0; i<i1; i++)
|
|
{
|
|
for(int j=j0; j<j1; j++)
|
|
{
|
|
if(j>=i)
|
|
{
|
|
v=0.0;
|
|
if(disttype==0)
|
|
for(int k=0; k<nfeatures; k++)
|
|
{
|
|
vv=MathAbs(xy.Get(i,k)-xy.Get(j,k));
|
|
if(vv>v)
|
|
v=vv;
|
|
}
|
|
else
|
|
if(disttype==1)
|
|
for(int k=0; k<nfeatures; k++)
|
|
{
|
|
vv=MathAbs(xy.Get(i,k)-xy.Get(j,k));
|
|
v+=vv;
|
|
}
|
|
d.Set(i,j,v);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CFilters
|
|
{
|
|
public:
|
|
static void FilterSMA(CRowDouble &x,int n,int k);
|
|
static void FilterEMA(CRowDouble &x,int n,double alpha);
|
|
static void FilterLRMA(CRowDouble &x,int n,int k);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Filters: simple moving averages (unsymmetric). |
|
|
//| This filter replaces array by results of SMA(K) filter. SMA(K) |
|
|
//| is defined as filter which averages at most K previous points |
|
|
//| (previous - not points AROUND central point) - or less, in case |
|
|
//| of the first K-1 points. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[N], array to process. It can be larger |
|
|
//| than N, in this case only first N points are |
|
|
//| processed. |
|
|
//| N - points count, N>=0 |
|
|
//| K - K>=1 (K can be larger than N, such cases will |
|
|
//| be correctly handled). Window width. K=1 |
|
|
//| corresponds to identity transformation (nothing |
|
|
//| changes). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array, whose first N elements were processed |
|
|
//| with SMA(K) |
|
|
//| NOTE 1: this function uses efficient in-place algorithm which |
|
|
//| does not allocate temporary arrays. |
|
|
//| NOTE 2: this algorithm makes only one pass through array and |
|
|
//| uses running sum to speed-up calculation of the averages.|
|
|
//| Additional measures are taken to ensure that running sum |
|
|
//| on a long sequence of zero elements will be correctly |
|
|
//| reset to zero even in the presence of round-off error. |
|
|
//| NOTE 3: this is unsymmetric version of the algorithm, which does |
|
|
//| NOT averages points after the current one. Only |
|
|
//| X[i], X[i-1], ... are used when calculating new value |
|
|
//| of X[i]. We should also note that this algorithm uses |
|
|
//| BOTH previous points and current one, i.e. new value |
|
|
//| of X[i] depends on BOTH previous point and X[i] itself. |
|
|
//+------------------------------------------------------------------+
|
|
void CFilters::FilterSMA(CRowDouble &x,int n,int k)
|
|
{
|
|
//--- Quick exit, if necessary
|
|
if(n<=1 || k==1)
|
|
return;
|
|
//--- create variables
|
|
double runningsum=0;
|
|
double termsinsum=0;
|
|
int zeroprefix=0;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
|
|
return;
|
|
if(!CAp::Assert(x.Size()>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains INF or NAN"))
|
|
return;
|
|
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1"))
|
|
return;
|
|
//--- Prepare variables (see below for explanation)
|
|
runningsum=0.0;
|
|
termsinsum=0;
|
|
for(int i=MathMax(n-k,0); i<n; i++)
|
|
{
|
|
runningsum=runningsum+x[i];
|
|
termsinsum++;
|
|
}
|
|
int i=MathMax(n-k,0);
|
|
zeroprefix=0;
|
|
while(i<n && x[i]==0.0)
|
|
{
|
|
zeroprefix++;
|
|
i++;
|
|
}
|
|
//--- General case: we assume that N>1 and K>1
|
|
//--- Make one pass through all elements. At the beginning of
|
|
//--- the iteration we have:
|
|
//--- * I element being processed
|
|
//--- * RunningSum current value of the running sum
|
|
//--- (including I-th element)
|
|
//--- * TermsInSum number of terms in sum, 0<=TermsInSum<=K
|
|
//--- * ZeroPrefix length of the sequence of zero elements
|
|
//--- which starts at X[I-K+1] and continues towards X[I].
|
|
//--- Equal to zero in case X[I-K+1] is non-zero.
|
|
//--- This value is used to make RunningSum exactly zero
|
|
//--- when it follows from the problem properties.
|
|
for(i=n-1; i>=0; i--)
|
|
{
|
|
//--- Store new value of X[i], save old value in V
|
|
v=x[i];
|
|
x.Set(i,runningsum/termsinsum);
|
|
//--- Update RunningSum and TermsInSum
|
|
if(i-k>=0)
|
|
runningsum+=x[i-k]-v;
|
|
else
|
|
{
|
|
runningsum-=v;
|
|
termsinsum --;
|
|
}
|
|
//--- Update ZeroPrefix.
|
|
//--- In case we have ZeroPrefix=TermsInSum,
|
|
//--- RunningSum is reset to zero.
|
|
if(i-k>=0)
|
|
{
|
|
if(x[i-k]!=0.0)
|
|
zeroprefix=0;
|
|
else
|
|
zeroprefix=MathMin(zeroprefix+1,k);
|
|
}
|
|
else
|
|
{
|
|
zeroprefix=MathMin(zeroprefix,i+1);
|
|
}
|
|
if(zeroprefix==termsinsum)
|
|
runningsum=0;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Filters: exponential moving averages. |
|
|
//| This filter replaces array by results of EMA(alpha) filter. |
|
|
//| EMA(alpha) is defined as filter which replaces X[] by S[]: |
|
|
//| S[0] = X[0] |
|
|
//| S[t] = alpha * X[t] + (1 - alpha) * S[t - 1] |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[N], array to process. It can be larger |
|
|
//| than N, in this case only first N points are |
|
|
//| processed. |
|
|
//| N - points count, N >= 0 |
|
|
//| alpha - 0 < alpha <= 1, smoothing parameter. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array, whose first N elements were processed |
|
|
//| with EMA(alpha) |
|
|
//| NOTE 1: this function uses efficient in-place algorithm which |
|
|
//| does not allocate temporary arrays. |
|
|
//| NOTE 2: this algorithm uses BOTH previous points and current one,|
|
|
//| i.e. new value of X[i] depends on BOTH previous point and|
|
|
//| X[i] itself. |
|
|
//| NOTE 3: technical analytis users quite often work with EMA |
|
|
//| coefficient expressed in DAYS instead of fractions. If |
|
|
//| you want to calculate EMA(N), where N is a number of |
|
|
//| days, you can use alpha = 2 / (N + 1). |
|
|
//+------------------------------------------------------------------+
|
|
void CFilters::FilterEMA(CRowDouble &x,int n,double alpha)
|
|
{
|
|
//--- Quick exit, if necessary
|
|
if(n<=1 || alpha==1.0)
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
|
|
return;
|
|
if(!CAp::Assert(x.Size()>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains INF or NAN"))
|
|
return;
|
|
if(!CAp::Assert(alpha>0.0,__FUNCTION__+": Alpha<=0"))
|
|
return;
|
|
if(!CAp::Assert(alpha<=1.0,__FUNCTION__+": Alpha>1"))
|
|
return;
|
|
//--- Process
|
|
for(int i=1; i<n; i++)
|
|
x.Set(i,alpha*x[i]+(1-alpha)*x[i-1]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Filters: linear regression moving averages. |
|
|
//| This filter replaces array by results of LRMA(K) filter. |
|
|
//| LRMA(K) is defined as filter which, for each data point, builds |
|
|
//| linear regression model using K prevous points (point itself is |
|
|
//| included in these K points) and calculates value of this linear |
|
|
//| model at the point in question. |
|
|
//| INPUT PARAMETERS: |
|
|
//| X - array[N], array to process. It can be larger |
|
|
//| than N, in this case only first N points are |
|
|
//| processed. |
|
|
//| N - points count, N >= 0 |
|
|
//| K - K >= 1(K can be larger than N, such cases will |
|
|
//| be correctly handled). Window width. K = 1 |
|
|
//| corresponds to identity transformation(nothing |
|
|
//| changes). |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| X - array, whose first N elements were processed |
|
|
//| with SMA(K) |
|
|
//| NOTE 1: this function uses efficient in-place algorithm which |
|
|
//| does not allocate temporary arrays. |
|
|
//| NOTE 2: this algorithm makes only one pass through array and |
|
|
//| uses running sum to speed-up calculation of the averages.|
|
|
//| Additional measures are taken to ensure that running sum |
|
|
//| on a long sequence of zero elements will be correctly |
|
|
//| reset to zero even in the presence of round - off error. |
|
|
//| NOTE 3: this is unsymmetric version of the algorithm, which does |
|
|
//| NOT averages points after the current one. Only |
|
|
//| X[i], X[i - 1], ... are used when calculating new value |
|
|
//| of X[i]. We should also note that this algorithm uses |
|
|
//| BOTH previous points and current one, i.e. new value of |
|
|
//| X[i] depends on BOTH previous point and X[i] itself. |
|
|
//+------------------------------------------------------------------+
|
|
void CFilters::FilterLRMA(CRowDouble &x,int n,int k)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
|
|
return;
|
|
if(!CAp::Assert(x.Size()>=n,__FUNCTION__+": Length(X)<N"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains INF or NAN"))
|
|
return;
|
|
if(!CAp::Assert(k>=1,__FUNCTION__+": K<1"))
|
|
return;
|
|
//--- Quick exit, if necessary:
|
|
//--- * either N is equal to 1 (nothing to average)
|
|
//--- * or K is 1 (only point itself is used) or 2 (model is too simple,
|
|
//--- we will always get identity transformation)
|
|
if(n<=1 || k<=2)
|
|
return;
|
|
//--- create variables
|
|
CMatrixDouble xy;
|
|
CRowDouble s;
|
|
int info=0;
|
|
double a=0;
|
|
double b=0;
|
|
double vara=0;
|
|
double varb=0;
|
|
double covab=0;
|
|
double corrab=0;
|
|
double p=0;
|
|
int m=0;
|
|
int i1_=0;
|
|
//--- General case: K>2, N>1.
|
|
//--- We do not process points with I<2 because first two points (I=0 and I=1) will be
|
|
//--- left unmodified by LRMA filter in any case.
|
|
xy.Resize(k,2);
|
|
s=vector<double>::Ones(k);
|
|
for(int i=0; i<k; i++)
|
|
xy.Set(i,0,i);
|
|
for(int i=n-1; i>=2; i--)
|
|
{
|
|
m=MathMin(i+1,k);
|
|
i1_=i-m+1;
|
|
for(int i_=0; i_<m; i_++)
|
|
xy.Set(i_,1,x[i_+i1_]);
|
|
CLinReg::LRLines(xy,s,m,info,a,b,vara,varb,covab,corrab,p);
|
|
if(!CAp::Assert(info==1,__FUNCTION__+": internal error"))
|
|
return;
|
|
x.Set(i,a+b*(m-1));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This object stores state of the SSA model. |
|
|
//| You should use ALGLIB functions to work with this object. |
|
|
//+------------------------------------------------------------------+
|
|
struct CSSAModel
|
|
{
|
|
int m_NSequences;
|
|
CRowInt m_SequenceIdx;
|
|
CRowDouble m_SequenceData;
|
|
int m_AlgoType;
|
|
int m_WindowWidth;
|
|
int m_RtPowerUp;
|
|
int m_TopK;
|
|
int m_PrecomputedWidth;
|
|
int m_PrecomputedNBasis;
|
|
CMatrixDouble m_PrecomputedBasis;
|
|
int m_DefaultSubspaceits;
|
|
int m_MemoryLimit;
|
|
bool m_AreBasisAndSolverValid;
|
|
CMatrixDouble m_Basis;
|
|
CMatrixDouble m_BasisT;
|
|
CRowDouble m_SV;
|
|
CRowDouble m_ForecastA;
|
|
int m_NBasis;
|
|
CEigSubSpaceState m_Solver;
|
|
CMatrixDouble m_XXT;
|
|
CHighQualityRandState m_RS;
|
|
int m_RngSeed;
|
|
CRowInt m_Rtqueue;
|
|
int m_RtqueueCnt;
|
|
int m_RtqueueChunk;
|
|
int m_DbgCntEVD;
|
|
CRowDouble m_Tmp0;
|
|
CRowDouble m_Tmp1;
|
|
CEigSubSpaceReport m_SolverRep;
|
|
CRowDouble m_AlongTrend;
|
|
CRowDouble m_AlongNoise;
|
|
CMatrixDouble m_AseqTrajectory;
|
|
CMatrixDouble m_AseqTbProduct;
|
|
CRowInt m_AseqCounts;
|
|
CRowDouble m_FcTrend;
|
|
CRowDouble m_FcNoise;
|
|
CMatrixDouble m_FcTrendM;
|
|
CMatrixDouble m_UxBatch;
|
|
int m_UxBatchWidth;
|
|
int m_UxBatchSize;
|
|
int m_UxBatchLimit;
|
|
//---
|
|
CSSAModel(void);
|
|
~CSSAModel(void) {}
|
|
void Copy(const CSSAModel &obj);
|
|
//--- overloading
|
|
void operator=(const CSSAModel &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CSSAModel::CSSAModel(void)
|
|
{
|
|
m_NSequences=0;
|
|
m_AlgoType=0;
|
|
m_WindowWidth=0;
|
|
m_RtPowerUp=0;
|
|
m_TopK=0;
|
|
m_PrecomputedWidth=0;
|
|
m_PrecomputedNBasis=0;
|
|
m_DefaultSubspaceits=0;
|
|
m_MemoryLimit=0;
|
|
m_AreBasisAndSolverValid=false;
|
|
m_NBasis=0;
|
|
m_RngSeed=0;
|
|
m_RtqueueCnt=0;
|
|
m_RtqueueChunk=0;
|
|
m_DbgCntEVD=0;
|
|
m_UxBatchWidth=0;
|
|
m_UxBatchSize=0;
|
|
m_UxBatchLimit=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CSSAModel::Copy(const CSSAModel &obj)
|
|
{
|
|
m_NSequences=obj.m_NSequences;
|
|
m_SequenceIdx=obj.m_SequenceIdx;
|
|
m_SequenceData=obj.m_SequenceData;
|
|
m_AlgoType=obj.m_AlgoType;
|
|
m_WindowWidth=obj.m_WindowWidth;
|
|
m_RtPowerUp=obj.m_RtPowerUp;
|
|
m_TopK=obj.m_TopK;
|
|
m_PrecomputedWidth=obj.m_PrecomputedWidth;
|
|
m_PrecomputedNBasis=obj.m_PrecomputedNBasis;
|
|
m_PrecomputedBasis=obj.m_PrecomputedBasis;
|
|
m_DefaultSubspaceits=obj.m_DefaultSubspaceits;
|
|
m_MemoryLimit=obj.m_MemoryLimit;
|
|
m_AreBasisAndSolverValid=obj.m_AreBasisAndSolverValid;
|
|
m_Basis=obj.m_Basis;
|
|
m_BasisT=obj.m_BasisT;
|
|
m_SV=obj.m_SV;
|
|
m_ForecastA=obj.m_ForecastA;
|
|
m_NBasis=obj.m_NBasis;
|
|
m_Solver=obj.m_Solver;
|
|
m_XXT=obj.m_XXT;
|
|
m_RS=obj.m_RS;
|
|
m_RngSeed=obj.m_RngSeed;
|
|
m_Rtqueue=obj.m_Rtqueue;
|
|
m_RtqueueCnt=obj.m_RtqueueCnt;
|
|
m_RtqueueChunk=obj.m_RtqueueChunk;
|
|
m_DbgCntEVD=obj.m_DbgCntEVD;
|
|
m_Tmp0=obj.m_Tmp0;
|
|
m_Tmp1=obj.m_Tmp1;
|
|
m_SolverRep=obj.m_SolverRep;
|
|
m_AlongTrend=obj.m_AlongTrend;
|
|
m_AlongNoise=obj.m_AlongNoise;
|
|
m_AseqTrajectory=obj.m_AseqTrajectory;
|
|
m_AseqTbProduct=obj.m_AseqTbProduct;
|
|
m_AseqCounts=obj.m_AseqCounts;
|
|
m_FcTrend=obj.m_FcTrend;
|
|
m_FcNoise=obj.m_FcNoise;
|
|
m_FcTrendM=obj.m_FcTrendM;
|
|
m_UxBatch=obj.m_UxBatch;
|
|
m_UxBatchWidth=obj.m_UxBatchWidth;
|
|
m_UxBatchSize=obj.m_UxBatchSize;
|
|
m_UxBatchLimit=obj.m_UxBatchLimit;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CSSA
|
|
{
|
|
public:
|
|
static void SSACreate(CSSAModel &s);
|
|
static void SSASetWindow(CSSAModel &s,int windowwidth);
|
|
static void SSASetSeed(CSSAModel &s,int seed);
|
|
static void SSASetPowerUpLength(CSSAModel &s,int pwlen);
|
|
static void SSASetMemoryLimit(CSSAModel &s,int memlimit);
|
|
static void SSAAddSequence(CSSAModel &s,CRowDouble &x,int n);
|
|
static void SSAAppendPointAndUpdate(CSSAModel &s,double x,double updateits);
|
|
static void SSAAppendSequenceAndUpdate(CSSAModel &s,CRowDouble &x,int nticks,double updateits);
|
|
static void SSASetAlgoPrecomputed(CSSAModel &s,CMatrixDouble &a,int windowwidth,int nbasis);
|
|
static void SSASetAlgoTopKDirect(CSSAModel &s,int topk);
|
|
static void SSASetAlgoTopKRealtime(CSSAModel &s,int topk);
|
|
static void SSAClearData(CSSAModel &s);
|
|
static void SSAGetBasis(CSSAModel &s,CMatrixDouble &a,CRowDouble &sv,int &windowwidth,int &nbasis);
|
|
static void SSAGetLRR(CSSAModel &s,CRowDouble &a,int &windowwidth);
|
|
static void SSAAnalyzeLastWindow(CSSAModel &s,CRowDouble &trend,CRowDouble &noise,int &nticks);
|
|
static void SSAAnalyzeLast(CSSAModel &s,int nticks,CRowDouble &trend,CRowDouble &noise);
|
|
static void SSAAnalyzeSequence(CSSAModel &s,CRowDouble &data,int nticks,CRowDouble &trend,CRowDouble &noise);
|
|
static void SSAForecastLast(CSSAModel &s,int nticks,CRowDouble &trend);
|
|
static void SSAForecastSequence(CSSAModel &s,CRowDouble &data,int datalen,int forecastlen,bool applysmoothing,CRowDouble &trend);
|
|
static void SSAForecastAvgLast(CSSAModel &s,int m,int nticks,CRowDouble &trend);
|
|
static void SSAForecastAvgSequence(CSSAModel &s,CRowDouble &data,int datalen,int m,int forecastlen,bool applysmoothing,CRowDouble &trend);
|
|
|
|
private:
|
|
static bool HasSomethingToAnalyze(CSSAModel &s);
|
|
static bool IsSequenceBigEnough(CSSAModel &s,int i);
|
|
static void UpdateBasis(CSSAModel &s,int appendlen,double updateits);
|
|
static void AnalyzeSequence(CSSAModel &s,CRowDouble &data,int i0,int i1,CRowDouble &trend,CRowDouble &noise,int offs);
|
|
static void ForecastAvgSequence(CSSAModel &s,CRowDouble &data,int i0,int i1,int m,int forecastlen,bool smooth,CRowDouble &trend,int offs);
|
|
static void RealtimeDequeue(CSSAModel &s,double beta,int cnt);
|
|
static void UpdateXXTPrepare(CSSAModel &s,int updatesize,int windowwidth,int memorylimit);
|
|
static void UpdateXXTSend(CSSAModel &s,CRowDouble &u,int i0,CMatrixDouble &xxt);
|
|
static void UpdateXXTFinalize(CSSAModel &s,CMatrixDouble &xxt);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| This function creates SSA model object. Right after creation |
|
|
//| model is in "dummy" mode - you can add data, but analyzing / |
|
|
//| prediction will return just zeros (it assumes that basis is |
|
|
//| empty). |
|
|
//| HOW TO USE SSA MODEL: |
|
|
//| 1. create model with SSACreate() |
|
|
//| 2. add data with one/many SSAAddSequence() calls |
|
|
//| 3. choose SSA algorithm with one of SSASetAlgo...() functions: |
|
|
//| * SSASetAlgoTopKDirect() for direct one-run analysis |
|
|
//| * SSASetAlgoTopKRealtime() for algorithm optimized for many |
|
|
//| subsequent runs with warm-start capabilities |
|
|
//| * SSASetAlgoPrecomputed() for user-supplied basis |
|
|
//| 4. set window width with SSASetWindow() |
|
|
//| 5. perform one of the analysis-related activities: |
|
|
//| a) call SSAGetBasis() to get basis |
|
|
//| b) call SSAAnalyzeLast() SSAAnalyzeSequence() or |
|
|
//| SSAAnalyzeLastWindow() to perform analysis (trend/noise |
|
|
//| separation) |
|
|
//| c) call one of the forecasting functions (SSAForecastLast() |
|
|
//| or SSAForecastSequence()) to perform prediction; |
|
|
//| alternatively, you can extract linear recurrence |
|
|
//| coefficients with SSAGetLRR(). |
|
|
//| SSA analysis will be performed during first call to analysis - |
|
|
//| related function. SSA model is smart enough to track all changes |
|
|
//| in the dataset and model settings, to cache previously computed |
|
|
//| basis and to re-evaluate basis only when necessary. |
|
|
//| Additionally, if your setting involves constant stream of |
|
|
//| incoming data, you can perform quick update already calculated |
|
|
//| model with one of the incremental append-and-update functions: |
|
|
//| SSAAppendPointAndUpdate() or SSAAppendSequenceAndUpdate(). |
|
|
//| NOTE: steps (2), (3), (4) can be performed in arbitrary order. |
|
|
//| INPUT PARAMETERS: |
|
|
//| none |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - structure which stores model state |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSACreate(CSSAModel &s)
|
|
{
|
|
//--- Model data, algorithms and settings
|
|
s.m_NSequences=0;
|
|
s.m_SequenceIdx.Resize(1);
|
|
s.m_SequenceIdx.Set(0,0);
|
|
s.m_AlgoType=0;
|
|
s.m_WindowWidth=1;
|
|
s.m_RtPowerUp=1;
|
|
s.m_AreBasisAndSolverValid=false;
|
|
s.m_RngSeed=1;
|
|
s.m_DefaultSubspaceits=10;
|
|
s.m_MemoryLimit=50000000;
|
|
//--- Debug counters
|
|
s.m_DbgCntEVD=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets window width for SSA model. You should call it|
|
|
//| before analysis phase. Default window width is 1 (not for real |
|
|
//| use). |
|
|
//| Special notes: |
|
|
//| * this function call can be performed at any moment before |
|
|
//| first call to analysis-related functions |
|
|
//| * changing window width invalidates internally stored basis; |
|
|
//| if you change window width AFTER you call analysis-related |
|
|
//| function, next analysis phase will require re-calculation of |
|
|
//| the basis according to current algorithm. |
|
|
//| * calling this function with exactly same window width as |
|
|
//| current one has no effect |
|
|
//| * if you specify window width larger than any data sequence |
|
|
//| stored in the model, analysis will return zero basis. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model created with SSACreate() |
|
|
//| WindowWidth - >=1, new window width |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - SSA model, updated |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSASetWindow(CSSAModel &s,int windowwidth)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(windowwidth>=1,__FUNCTION__+": WindowWidth<1"))
|
|
return;
|
|
if(windowwidth==s.m_WindowWidth)
|
|
return;
|
|
|
|
s.m_WindowWidth=windowwidth;
|
|
s.m_AreBasisAndSolverValid=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets seed which is used to initialize internal RNG |
|
|
//| when we make pseudorandom decisions on model updates. |
|
|
//| By default, deterministic seed is used - which results in same |
|
|
//| sequence of pseudorandom decisions every time you run SSA model. |
|
|
//| If you specify non-deterministic seed value, then SSA model may |
|
|
//| return slightly different results after each run. |
|
|
//| This function can be useful when you have several SSA models |
|
|
//| updated with SSAAppendPointAndUpdate() called with 0<UpdateIts<1 |
|
|
//| (fractional value) and due to performance limitations want them |
|
|
//| to perform updates at different moments. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| Seed - seed: |
|
|
//| * positive values = use deterministic seed for |
|
|
//| each run of algorithms which depend on random |
|
|
//| initialization |
|
|
//| * zero or negative values=use non-deterministic |
|
|
//| seed |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSASetSeed(CSSAModel &s,int seed)
|
|
{
|
|
s.m_RngSeed=seed;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets length of power-up cycle for real-time |
|
|
//| algorithm. |
|
|
//| By default, this algorithm performs costly O(N*WindowWidth^2) |
|
|
//| init phase followed by full run of truncated EVD. However, if you|
|
|
//| are ready to live with a bit lower-quality basis during first few|
|
|
//| iterations, you can split this O(N*WindowWidth^2) initialization |
|
|
//| between several subsequent append-and-update rounds. It results |
|
|
//| in better latency of the algorithm. |
|
|
//| This function invalidates basis/solver, next analysis call will |
|
|
//| result in full recalculation of everything. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| PWLen - length of the power-up stage: |
|
|
//| * 0 means that no power-up is requested |
|
|
//| * 1 is the same as 0 |
|
|
//| * >1 means that delayed power-up is performed |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSASetPowerUpLength(CSSAModel &s,int pwlen)
|
|
{
|
|
if(!CAp::Assert(pwlen>=0,__FUNCTION__+": PWLen<0"))
|
|
return;
|
|
|
|
s.m_RtPowerUp=MathMax(pwlen,1);
|
|
s.m_AreBasisAndSolverValid=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets memory limit of SSA analysis. |
|
|
//| Straightforward SSA with sequence length T and window width W |
|
|
//| needs O(T*W) memory. It is possible to reduce memory consumption |
|
|
//| by splitting task into smaller chunks. |
|
|
//| Thus function allows you to specify approximate memory limit |
|
|
//| (measured in double precision numbers used for buffers). Actual |
|
|
//| memory consumption will be comparable to the number specified by |
|
|
//| you. |
|
|
//| Default memory limit is 50.000.000 (400Mbytes) in current |
|
|
//| version. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| MemLimit - memory limit, >=0. Zero value means no limit. |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSASetMemoryLimit(CSSAModel &s,int memlimit)
|
|
{
|
|
if(memlimit<0)
|
|
memlimit=0;
|
|
s.m_MemoryLimit=memlimit;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function adds data sequence to SSA model. Only single- |
|
|
//| dimensional sequences are supported. |
|
|
//| What is a sequences? Following definitions/requirements apply: |
|
|
//| * a sequence is an array of values measured in subsequent, |
|
|
//| equally separated time moments (ticks). |
|
|
//| * you may have many sequences in your dataset; say, one |
|
|
//| sequence may correspond to one trading session. |
|
|
//| * sequence length should be larger than current window length |
|
|
//| (shorter sequences will be ignored during analysis). |
|
|
//| * analysis is performed within a sequence; different sequences |
|
|
//| are NOT stacked together to produce one large contiguous |
|
|
//| stream of data. |
|
|
//| * analysis is performed for all sequences at once, i.e. same |
|
|
//| set of basis vectors is computed for all sequences |
|
|
//| INCREMENTAL ANALYSIS |
|
|
//| This function is non intended for incremental updates of |
|
|
//| previously found SSA basis. Calling it invalidates all previous |
|
|
//| analysis results (basis is reset and will be recalculated from |
|
|
//| zero during next analysis). |
|
|
//| If you want to perform incremental/real-time SSA, consider using |
|
|
//| following functions: |
|
|
//| * SSAAppendPointAndUpdate() for appending one point |
|
|
//| * SSAAppendSequenceAndUpdate() for appending new sequence |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model created with SSACreate() |
|
|
//| X - array[N], data, can be larger (additional values|
|
|
//| are ignored) |
|
|
//| N - data length, can be automatically determined |
|
|
//| from the array length. N>=0. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - SSA model, updated |
|
|
//| NOTE: you can clear dataset with SSAClearData() |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSAAddSequence(CSSAModel &s,CRowDouble &x,int n)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(n>=0,__FUNCTION__+": N<0"))
|
|
return;
|
|
if(!CAp::Assert(x.Size()>=n,__FUNCTION__+": X is too short"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,n),__FUNCTION__+": X contains infinities NANs"))
|
|
return;
|
|
//--- Invalidate model
|
|
s.m_AreBasisAndSolverValid=false;
|
|
//--- Add sequence
|
|
s.m_SequenceIdx.Resize(s.m_NSequences+2);
|
|
s.m_SequenceIdx.Set(s.m_NSequences+1,s.m_SequenceIdx[s.m_NSequences]+n);
|
|
s.m_SequenceData.Resize(s.m_SequenceIdx[s.m_NSequences+1]);
|
|
|
|
int offs=s.m_SequenceIdx[s.m_NSequences];
|
|
for(int i=0; i<n; i++)
|
|
s.m_SequenceData.Set(offs+i,x[i]);
|
|
s.m_NSequences++;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function appends single point to last data sequence stored |
|
|
//| in the SSA model and tries to update model in the incremental |
|
|
//| manner (if possible with current algorithm). |
|
|
//| If you want to add more than one point at once: |
|
|
//| * if you want to add M points to the same sequence, perform |
|
|
//| M-1 calls with UpdateIts parameter set to 0.0, and last call |
|
|
//| with non-zero UpdateIts. |
|
|
//| * if you want to add new sequence, use |
|
|
//| SSAAppendSequenceAndUpdate() |
|
|
//| Running time of this function does NOT depend on dataset size, |
|
|
//| only on window width and number of singular vectors. Depending |
|
|
//| on algorithm being used, incremental update has complexity: |
|
|
//| * for top-K real time - O(UpdateIts*K*Width^2), with |
|
|
//| fractional UpdateIts |
|
|
//| * for top-K direct - O(Width^3) for any non-zero |
|
|
//| UpdateIts |
|
|
//| * for precomputed basis - O(1), no update is performed |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model created with SSACreate() |
|
|
//| X - new point |
|
|
//| UpdateIts - >=0, floating point(!) value, desired update |
|
|
//| frequency: |
|
|
//| * zero value means that point is stored, but no update|
|
|
//| is performed |
|
|
//| * integer part of the value means that specified |
|
|
//| number of iterations is always performed |
|
|
//| * fractional part of the value means that one |
|
|
//| iteration is performed with this probability. |
|
|
//| Recommended value: 0<UpdateIts<=1. Values larger than 1 are VERY |
|
|
//| seldom needed. If your dataset changes slowly, you can set it to |
|
|
//| 0.1 and skip 90% of updates. |
|
|
//| In any case, no information is lost even with zero value of |
|
|
//| UpdateIts! It will be incorporated into model, sooner or later. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - SSA model, updated |
|
|
//| NOTE: this function uses internal RNG to handle fractional values|
|
|
//| of UpdateIts. By default it is initialized with fixed seed |
|
|
//| during initial calculation of basis. Thus subsequent calls |
|
|
//| to this function will result in the same sequence of |
|
|
//| pseudorandom decisions. |
|
|
//| However, if you have several SSA models which are calculated |
|
|
//| simultaneously, and if you want to reduce computational |
|
|
//| bottlenecks by performing random updates at random moments, then |
|
|
//| fixed seed is not an option - all updates will fire at same |
|
|
//| moments. |
|
|
//| You may change it with SSASetSeed() function. |
|
|
//| NOTE: this function throws an exception if called for empty |
|
|
//| dataset (there is no "last" sequence to modify). |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSAAppendPointAndUpdate(CSSAModel &s,double x,double updateits)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(x),__FUNCTION__+": X is not finite"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(MathIsValidNumber(updateits),__FUNCTION__+": UpdateIts is not finite"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(updateits>=0.0,__FUNCTION__+": UpdateIts<0"))
|
|
return;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": dataset is empty,no sequence to modify"))
|
|
return;
|
|
//--- Append point to dataset
|
|
s.m_SequenceData.Resize(s.m_SequenceIdx[s.m_NSequences]+1);
|
|
s.m_SequenceData.Set(s.m_SequenceIdx[s.m_NSequences],x);
|
|
s.m_SequenceIdx.Set(s.m_NSequences,s.m_SequenceIdx[s.m_NSequences]+1);
|
|
//--- Do we have something to analyze? If no, invalidate basis
|
|
//--- (just to be sure) and exit.
|
|
if(!HasSomethingToAnalyze(s))
|
|
{
|
|
s.m_AreBasisAndSolverValid=false;
|
|
return;
|
|
}
|
|
//--- Well, we have data to analyze and algorithm set, but basis is
|
|
//--- invalid. Let's calculate it from scratch and exit.
|
|
if(!s.m_AreBasisAndSolverValid)
|
|
{
|
|
UpdateBasis(s,0,0.0);
|
|
return;
|
|
}
|
|
//--- Update already computed basis
|
|
UpdateBasis(s,1,updateits);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function appends new sequence to dataset stored in the SSA |
|
|
//| model and tries to update model in the incremental manner (if |
|
|
//| possible with current algorithm). |
|
|
//| Notes: |
|
|
//| * if you want to add M sequences at once, perform M-1 calls |
|
|
//| with UpdateIts parameter set to 0.0, and last call with |
|
|
//| non-zero UpdateIts. |
|
|
//| * if you want to add just one point, use |
|
|
//| SSAAppendPointAndUpdate() |
|
|
//| Running time of this function does NOT depend on dataset size, |
|
|
//| only on sequence length, window width and number of singular |
|
|
//| vectors. Depending on algorithm being used, incremental update |
|
|
//| has complexity: |
|
|
//| * for top-K real time - O(UpdateIts*K*Width^2+ |
|
|
//| (NTicks-Width)*Width^2) |
|
|
//| * for top-K direct - O(Width^3+(NTicks-Width)*Width^2) |
|
|
//| * for precomputed basis - O(1), no update is performed |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model created with SSACreate() |
|
|
//| X - new sequence, array[NTicks] or larget |
|
|
//| NTicks - >=1, number of ticks in the sequence |
|
|
//| UpdateIts - >=0, floating point(!) value, desired update |
|
|
//| frequency: |
|
|
//| * zero value means that point is stored, but no |
|
|
//| update is performed |
|
|
//| * integer part of the value means that specified |
|
|
//| number of iterations is always performed |
|
|
//| * fractional part of the value means that one |
|
|
//| iteration is performed with this probability. |
|
|
//| Recommended value: 0<UpdateIts<=1. Values larger than 1 are VERY |
|
|
//| seldom needed. If your dataset changes slowly, you can set it to |
|
|
//| 0.1 and skip 90% of updates. |
|
|
//| In any case, no information is lost even with zero value of |
|
|
//| UpdateIts! It will be incorporated into model, sooner or later. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - SSA model, updated |
|
|
//| NOTE: this function uses internal RNG to handle fractional values|
|
|
//| of UpdateIts. By default it is initialized with fixed seed |
|
|
//| during initial calculation of basis. Thus subsequent calls |
|
|
//| to this function will result in the same sequence of |
|
|
//| pseudorandom decisions. |
|
|
//| However, if you have several SSA models which are calculated |
|
|
//| simultaneously, and if you want to reduce computational |
|
|
//| bottlenecks by performing random updates at random moments, then |
|
|
//| fixed seed is not an option - all updates will fire at same |
|
|
//| moments. |
|
|
//| You may change it with SSASetSeed() function. |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSAAppendSequenceAndUpdate(CSSAModel &s,CRowDouble &x,
|
|
int nticks,double updateits)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(nticks>=0,__FUNCTION__+": NTicks<0"))
|
|
return;
|
|
if(!CAp::Assert(x.Size()>=nticks,__FUNCTION__+": X is too short"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(x,nticks),__FUNCTION__+": X contains infinities NANs"))
|
|
return;
|
|
//--- Add sequence
|
|
s.m_SequenceIdx.Resize(s.m_NSequences+2);
|
|
s.m_SequenceIdx.Set(s.m_NSequences+1,s.m_SequenceIdx[s.m_NSequences]+nticks);
|
|
s.m_SequenceData.Resize(s.m_SequenceIdx[s.m_NSequences+1]);
|
|
|
|
int offs=s.m_SequenceIdx[s.m_NSequences];
|
|
for(int i=0; i<nticks; i++)
|
|
s.m_SequenceData.Set(offs+i,x[i]);
|
|
s.m_NSequences++;
|
|
//--- Do we have something to analyze? If no, invalidate basis
|
|
//--- (just to be sure) and exit.
|
|
if(!HasSomethingToAnalyze(s))
|
|
{
|
|
s.m_AreBasisAndSolverValid=false;
|
|
return;
|
|
}
|
|
//--- Well, we have data to analyze and algorithm set, but basis is
|
|
//--- invalid. Let's calculate it from scratch and exit.
|
|
if(!s.m_AreBasisAndSolverValid)
|
|
{
|
|
UpdateBasis(s,0,0.0);
|
|
return;
|
|
}
|
|
//--- Update already computed basis
|
|
if(nticks>=s.m_WindowWidth)
|
|
UpdateBasis(s,nticks-s.m_WindowWidth+1,updateits);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets SSA algorithm to "precomputed vectors" |
|
|
//| algorithm. |
|
|
//| This algorithm uses precomputed set of orthonormal(orthogonal |
|
|
//| AND normalized) basis vectors supplied by user. Thus, basis |
|
|
//| calculation phase is not performed - we already have our basis - |
|
|
//| and only analysis/forecasting phase requires actual calculations.|
|
|
//| This algorithm may handle "append" requests which add just one/ |
|
|
//| few ticks to the end of the last sequence in O(1) time. |
|
|
//| NOTE: this algorithm accepts both basis and window width, because|
|
|
//| these two parameters are naturally aligned. Calling this |
|
|
//| function sets window width; if you call SSASetWindow() |
|
|
//| with other window width, then during analysis stage |
|
|
//| algorithm will detect conflict and reset to zero basis. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| A - array[WindowWidth, NBasis], orthonormalized |
|
|
//| basis; this function does NOT control |
|
|
//| orthogonality and does NOT perform any kind of |
|
|
//| renormalization. It is your responsibility to |
|
|
//| provide it with correct basis. |
|
|
//| WindowWidth - window width, >= 1 |
|
|
//| NBasis - number of basis vectors, |
|
|
//| 1 <= NBasis <= WindowWidth |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - updated model |
|
|
//| NOTE: calling this function invalidates basis in all cases. |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSASetAlgoPrecomputed(CSSAModel &s,CMatrixDouble &a,
|
|
int windowwidth,int nbasis)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(windowwidth>=1,__FUNCTION__+": WindowWidth<1"))
|
|
return;
|
|
if(!CAp::Assert(nbasis>=1,__FUNCTION__+": NBasis<1"))
|
|
return;
|
|
if(!CAp::Assert(nbasis<=windowwidth,__FUNCTION__+": NBasis>WindowWidth"))
|
|
return;
|
|
if(!CAp::Assert(a.Rows()>=windowwidth,__FUNCTION__+": Rows(A)<WindowWidth"))
|
|
return;
|
|
if(!CAp::Assert(a.Cols()>=nbasis,__FUNCTION__+": Rows(A)<NBasis"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(a,windowwidth,nbasis),__FUNCTION__+": Rows(A)<NBasis"))
|
|
return;
|
|
|
|
s.m_AlgoType=1;
|
|
s.m_PrecomputedWidth=windowwidth;
|
|
s.m_PrecomputedNBasis=nbasis;
|
|
s.m_WindowWidth=windowwidth;
|
|
s.m_PrecomputedBasis=a;
|
|
s.m_PrecomputedBasis.Resize(windowwidth,nbasis);
|
|
s.m_AreBasisAndSolverValid=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets SSA algorithm to "direct top-K" algorithm. |
|
|
//| "Direct top-K" algorithm performs full SVD of the N*WINDOW |
|
|
//| trajectory matrix (hence its name - direct solver is used), then |
|
|
//| extracts top K components. Overall running time is |
|
|
//| O(N * WINDOW ^ 2), where N is a number of ticks in the dataset, |
|
|
//| WINDOW is window width. |
|
|
//| This algorithm may handle "append" requests which add just one / |
|
|
//| few ticks to the end of the last sequence in O(WINDOW ^ 3) time, |
|
|
//| which is ~N/WINDOW times faster than re-computing everything from|
|
|
//| scratch. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| TopK - number of components to analyze; TopK >= 1. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - updated model |
|
|
//| NOTE: TopK>WindowWidth is silently decreased to WindowWidth |
|
|
//| during analysis phase |
|
|
//| NOTE: calling this function invalidates basis, except for the |
|
|
//| situation when this algorithm was already set with same |
|
|
//| parameters. |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSASetAlgoTopKDirect(CSSAModel &s,int topk)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(topk>=1,__FUNCTION__+": TopK<1"))
|
|
return;
|
|
//--- Ignore calls which change nothing
|
|
if(s.m_AlgoType==2 && s.m_TopK==topk)
|
|
return;
|
|
//--- Update settings, invalidate model
|
|
s.m_AlgoType=2;
|
|
s.m_TopK=topk;
|
|
s.m_AreBasisAndSolverValid=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets SSA algorithm to "top-K real time algorithm". |
|
|
//| This algo extracts K components with largest singular values. |
|
|
//| It is real-time version of top-K algorithm which is optimized for|
|
|
//| incremental processing and fast start-up. Internally it uses |
|
|
//| subspace eigensolver for truncated SVD. It results in ability to |
|
|
//| perform quick updates of the basis when only a few points / |
|
|
//| sequences is added to dataset. |
|
|
//| Performance profile of the algorithm is given below: |
|
|
//| * O(K * WindowWidth ^ 2) running time for incremental update |
|
|
//| of the dataset with one of the "append-and-update" functions |
|
|
//| (SSAAppendPointAndUpdate() or SSAAppendSequenceAndUpdate()). |
|
|
//| * O(N * WindowWidth ^ 2) running time for initial basis |
|
|
//| evaluation(N = size of dataset) |
|
|
//| * ability to split costly initialization across several |
|
|
//| incremental updates of the basis(so called "Power-Up" |
|
|
//| functionality, activated by SSASetPowerUpLength() function) |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| TopK - number of components to analyze; TopK >= 1. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - updated model |
|
|
//| NOTE: this algorithm is optimized for large-scale tasks with |
|
|
//| large datasets. On toy problems with just 5-10 points it |
|
|
//| can return basis which is slightly different from that |
|
|
//| returned by direct algorithm (SSASetAlgoTopKDirect() |
|
|
//| function). However, the difference becomes negligible as |
|
|
//| dataset grows. |
|
|
//| NOTE: TopK > WindowWidth is silently decreased to WindowWidth |
|
|
//| during analysis phase |
|
|
//| NOTE: calling this function invalidates basis, except for the |
|
|
//| situation when this algorithm was already set with same |
|
|
//| parameters. |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSASetAlgoTopKRealtime(CSSAModel &s,int topk)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(topk>=1,__FUNCTION__+": TopK<1"))
|
|
return;
|
|
//--- Ignore calls which change nothing
|
|
if(s.m_AlgoType==3 && s.m_TopK==topk)
|
|
return;
|
|
//--- Update settings, invalidate model
|
|
s.m_AlgoType=3;
|
|
s.m_TopK=topk;
|
|
s.m_AreBasisAndSolverValid=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function clears all data stored in the model and invalidates|
|
|
//| all basis components found so far. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model created with SSACreate() |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - SSA model, updated |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSAClearData(CSSAModel &s)
|
|
{
|
|
s.m_NSequences=0;
|
|
s.m_AreBasisAndSolverValid=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function executes SSA on internally stored dataset and |
|
|
//| returns basis found by current method. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| A - array[WindowWidth, NBasis], basis; vectors are |
|
|
//| stored in matrix columns, by descreasing variance |
|
|
//| SV - array[NBasis]: |
|
|
//| * zeros - for model initialized with |
|
|
//| SSASetAlgoPrecomputed() |
|
|
//| * singular values - for other algorithms |
|
|
//| WindowWidth - current window |
|
|
//| NBasis - basis size |
|
|
//| CACHING / REUSE OF THE BASIS |
|
|
//| Caching / reuse of previous results is performed: |
|
|
//| * first call performs full run of SSA; basis is stored in the |
|
|
//| cache |
|
|
//| * subsequent calls reuse previously cached basis |
|
|
//| * if you call any function which changes model properties |
|
|
//| (window length, algorithm, dataset), internal basis will be |
|
|
//| invalidated. |
|
|
//| * the only calls which do NOT invalidate basis are listed |
|
|
//| below: |
|
|
//| a) SSASetWindow() with same window length |
|
|
//| b) SSAAppendPointAndUpdate() |
|
|
//| c) SSAAppendSequenceAndUpdate() |
|
|
//| d) SSASetAlgoTopK...() with exactly same K Calling these |
|
|
//| functions will result in reuse of previously found basis.|
|
|
//| HANDLING OF DEGENERATE CASES |
|
|
//| Calling this function in degenerate cases(no data or all data are|
|
|
//| shorter than window size; no algorithm is specified) returns |
|
|
//| basis with just one zero vector. |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSAGetBasis(CSSAModel &s,CMatrixDouble &a,
|
|
CRowDouble &sv,int &windowwidth,
|
|
int &nbasis)
|
|
{
|
|
//--- initialization
|
|
windowwidth=0;
|
|
nbasis=0;
|
|
//--- Is it degenerate case?
|
|
if(!HasSomethingToAnalyze(s))
|
|
{
|
|
windowwidth=s.m_WindowWidth;
|
|
nbasis=1;
|
|
a=matrix<double>::Zeros(windowwidth,1);
|
|
sv=vector<double>::Zeros(1);
|
|
return;
|
|
}
|
|
//--- Update basis.
|
|
//--- It will take care of basis validity flags. AppendLen=0 which means
|
|
//--- that we perform initial basis evaluation.
|
|
UpdateBasis(s,0,0.0);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_NBasis>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(s.m_WindowWidth>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- Output
|
|
nbasis=s.m_NBasis;
|
|
windowwidth=s.m_WindowWidth;
|
|
a=s.m_Basis;
|
|
sv=s.m_SV;
|
|
a.Resize(windowwidth,nbasis);
|
|
sv.Resize(nbasis);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns linear recurrence relation(LRR) |
|
|
//| coefficients found by current SSA algorithm. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| A - array[WindowWidth - 1]. Coefficients of the linear |
|
|
//| recurrence of the form: |
|
|
//| X[W - 1] = X[W - 2] * A[W - 2] + |
|
|
//| X[W - 3] * A[W - 3] + ... + X[0] * A[0].|
|
|
//| Empty array for WindowWidth = 1. |
|
|
//| WindowWidth - current window width |
|
|
//| CACHING / REUSE OF THE BASIS |
|
|
//| Caching / reuse of previous results is performed: |
|
|
//| * first call performs full run of SSA; basis is stored in the |
|
|
//| cache |
|
|
//| * subsequent calls reuse previously cached basis |
|
|
//| * if you call any function which changes model properties |
|
|
//| (window length, algorithm, dataset), internal basis will be |
|
|
//| invalidated. |
|
|
//| * the only calls which do NOT invalidate basis are listed |
|
|
//| below: |
|
|
//| a) SSASetWindow() with same window length |
|
|
//| b) SSAAppendPointAndUpdate() |
|
|
//| c) SSAAppendSequenceAndUpdate() |
|
|
//| d) SSASetAlgoTopK...() with exactly same K |
|
|
//| Calling these functions will result in reuse of previously found |
|
|
//| basis. |
|
|
//| HANDLING OF DEGENERATE CASES |
|
|
//| Calling this function in degenerate cases (no data or all data |
|
|
//| are shorter than window size; no algorithm is specified) returns |
|
|
//| zeros. |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSAGetLRR(CSSAModel &s,CRowDouble &a,int &windowwidth)
|
|
{
|
|
a.Resize(0);
|
|
windowwidth=0;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_WindowWidth>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
//--- Is it degenerate case?
|
|
if(!HasSomethingToAnalyze(s))
|
|
{
|
|
windowwidth=s.m_WindowWidth;
|
|
a=vector<double>::Zeros(windowwidth-1);
|
|
return;
|
|
}
|
|
//--- Update basis.
|
|
//--- It will take care of basis validity flags. AppendLen=0 which means
|
|
//--- that we perform initial basis evaluation.
|
|
UpdateBasis(s,0,0.0);
|
|
//--- Output
|
|
windowwidth=s.m_WindowWidth;
|
|
a=s.m_ForecastA;
|
|
a.Resize(windowwidth-1);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function executes SSA on internally stored dataset and |
|
|
//| returns analysis for the last window of the last sequence. Such |
|
|
//| analysis is an lightweight alternative for full scale |
|
|
//| reconstruction (see below). |
|
|
//| Typical use case for this function is real-time setting, when |
|
|
//| you are interested in quick-and-dirty (very quick and very dirty)|
|
|
//| processing of just a few last ticks of the trend. |
|
|
//| IMPORTANT: full scale SSA involves analysis of the ENTIRE |
|
|
//| dataset, with reconstruction being done for all |
|
|
//| positions of sliding window with subsequent |
|
|
//| hankelization (diagonal averaging) of the resulting |
|
|
//| matrix. |
|
|
//| Such analysis requires O((DataLen - Window)*Window*NBasis) FLOPs |
|
|
//| and can be quite costly. However, it has nice noise - canceling |
|
|
//| effects due to averaging. |
|
|
//| This function performs REDUCED analysis of the last window. It |
|
|
//| is much faster - just O(Window*NBasis), but its results are |
|
|
//| DIFFERENT from that of SSAAnalyzeLast(). In particular, first few|
|
|
//| points of the trend are much more prone to noise. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Trend - array[WindowSize], reconstructed trend line |
|
|
//| Noise - array[WindowSize], the rest of the signal; it |
|
|
//| holds that ActualData = Trend + Noise. |
|
|
//| NTicks - current WindowSize |
|
|
//| CACHING / REUSE OF THE BASIS |
|
|
//| Caching / reuse of previous results is performed: |
|
|
//| * first call performs full run of SSA; basis is stored in the |
|
|
//| cache |
|
|
//| * subsequent calls reuse previously cached basis |
|
|
//| * if you call any function which changes model properties |
|
|
//| (window length, algorithm, dataset), internal basis will |
|
|
//| be invalidated. |
|
|
//| * the only calls which do NOT invalidate basis are listed |
|
|
//| below: |
|
|
//| a) SSASetWindow() with same window length |
|
|
//| b) SSAAppendPointAndUpdate() |
|
|
//| c) SSAAppendSequenceAndUpdate() |
|
|
//| d) SSASetAlgoTopK...() with exactly same K |
|
|
//| Calling these functions will result in reuse of previously found |
|
|
//| basis. |
|
|
//| In any case, only basis is reused. Reconstruction is performed |
|
|
//| from scratch every time you call this function. |
|
|
//| HANDLING OF DEGENERATE CASES |
|
|
//| Following degenerate cases may happen: |
|
|
//| * dataset is empty(no analysis can be done) |
|
|
//| * all sequences are shorter than the window length, no analysis|
|
|
//| can be done |
|
|
//| * no algorithm is specified(no analysis can be done) |
|
|
//| * last sequence is shorter than the window length (analysis |
|
|
//| can be done, but we can not perform reconstruction on the |
|
|
//| last sequence) |
|
|
//| Calling this function in degenerate cases returns following |
|
|
//| result: |
|
|
//| * in any case, WindowWidth ticks is returned |
|
|
//| * trend is assumed to be zero |
|
|
//| * noise is initialized by the last sequence; if last sequence |
|
|
//| is shorter than the window size, it is moved to the end of |
|
|
//| the array, and the beginning of the noise array is filled by |
|
|
//| zeros |
|
|
//| No analysis is performed in degenerate cases (we immediately |
|
|
//| return dummy values, no basis is constructed). |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSAAnalyzeLastWindow(CSSAModel &s,CRowDouble &trend,
|
|
CRowDouble &noise,int &nticks)
|
|
{
|
|
//--- create variables
|
|
int offs=0;
|
|
int cnt=0;
|
|
//--- Init
|
|
nticks=s.m_WindowWidth;
|
|
trend.Resize(s.m_WindowWidth);
|
|
noise.Resize(s.m_WindowWidth);
|
|
//--- Is it degenerate case?
|
|
if(!HasSomethingToAnalyze(s) || !IsSequenceBigEnough(s,-1))
|
|
{
|
|
trend.Fill(0);
|
|
noise.Fill(0);
|
|
if(s.m_NSequences>=1)
|
|
{
|
|
cnt=MathMin(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1],nticks);
|
|
offs=s.m_SequenceIdx[s.m_NSequences]-cnt;
|
|
for(int i=0; i<cnt; i++)
|
|
noise.Set(nticks-cnt+i,s.m_SequenceData[offs+i]);
|
|
}
|
|
return;
|
|
}
|
|
//--- Update basis.
|
|
//--- It will take care of basis validity flags. AppendLen=0 which means
|
|
//--- that we perform initial basis evaluation.
|
|
UpdateBasis(s,0,0.0);
|
|
//--- Perform analysis of the last window
|
|
//--- check
|
|
if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_WindowWidth>=0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
CApServ::RVectorSetLengthAtLeast(s.m_Tmp0,s.m_NBasis);
|
|
CAblas::RMatrixGemVect(s.m_NBasis,s.m_WindowWidth,1.0,s.m_BasisT,0,0,0,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences]-s.m_WindowWidth,0.0,s.m_Tmp0,0);
|
|
CAblas::RMatrixGemVect(s.m_WindowWidth,s.m_NBasis,1.0,s.m_Basis,0,0,0,s.m_Tmp0,0,0.0,trend,0);
|
|
offs=s.m_SequenceIdx[s.m_NSequences]-s.m_WindowWidth;
|
|
cnt=s.m_WindowWidth;
|
|
for(int i=0; i<cnt; i++)
|
|
noise.Set(i,s.m_SequenceData[offs+i]-trend[i]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function: |
|
|
//| * builds SSA basis using internally stored(entire) dataset |
|
|
//| * returns reconstruction for the last NTicks of the last |
|
|
//| sequence |
|
|
//| If you want to analyze some other sequence, use |
|
|
//| SSAAnalyzeSequence(). |
|
|
//| Reconstruction phase involves generation of NTicks-WindowWidth |
|
|
//| sliding windows, their decomposition using empirical orthogonal |
|
|
//| functions found by SSA, followed by averaging of each data point |
|
|
//| across several overlapping windows. Thus, every point in the |
|
|
//| output trend is reconstructed using up to WindowWidth overlapping|
|
|
//| windows(WindowWidth windows exactly in the inner points, just one|
|
|
//| window at the extremal points). |
|
|
//| IMPORTANT: due to averaging this function returns different |
|
|
//| results for different values of NTicks. It is expected|
|
|
//| and not a bug. |
|
|
//| For example: |
|
|
//| * Trend[NTicks - 1] is always same because it is not averaged |
|
|
//| in any case(same applies to Trend[0]). |
|
|
//| * Trend[NTicks - 2] has different values fo NTicks=WindowWidth |
|
|
//| and NTicks=WindowWidth+1 because former case means that no |
|
|
//| averaging is performed, and latter case means that averaging |
|
|
//| using two sliding windows is performed. Larger values of |
|
|
//| NTicks produce same results as NTicks = WindowWidth + 1. |
|
|
//| * ...and so on... |
|
|
//| PERFORMANCE: this function has |
|
|
//| O((NTicks - WindowWidth) * WindowWidth*NBasis) |
|
|
//| running time. If you work in time-constrained setting and have |
|
|
//| to analyze just a few last ticks, choosing NTicks equal to |
|
|
//| WindowWidth + SmoothingLen, with SmoothingLen = 1...WindowWidth |
|
|
//| will result in good compromise between noise cancellation and |
|
|
//| analysis speed. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| NTicks - number of ticks to analyze, Nticks >= 1. |
|
|
//| * special case of NTicks<=WindowWidth is handled |
|
|
//| by analyzing last window and returning NTicks |
|
|
//| last ticks. |
|
|
//| * special case NTicks>LastSequenceLen is handled by |
|
|
//| prepending result with NTicks-LastSequenceLen zeros.|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Trend - array[NTicks], reconstructed trend line |
|
|
//| Noise - array[NTicks], the rest of the signal; it holds |
|
|
//| that ActualData = Trend + Noise. |
|
|
//| CACHING / REUSE OF THE BASIS |
|
|
//| Caching / reuse of previous results is performed: |
|
|
//| * first call performs full run of SSA; basis is stored in |
|
|
//| the cache |
|
|
//| * subsequent calls reuse previously cached basis |
|
|
//| * if you call any function which changes model properties |
|
|
//| (window length, algorithm, dataset), internal basis will |
|
|
//| be invalidated. |
|
|
//| * the only calls which do NOT invalidate basis are listed |
|
|
//| below: |
|
|
//| a) SSASetWindow() with same window length |
|
|
//| b) SSAAppendPointAndUpdate() |
|
|
//| c) SSAAppendSequenceAndUpdate() |
|
|
//| d) SSASetAlgoTopK...() with exactly same K |
|
|
//| Calling these functions will result in reuse of previously found |
|
|
//| basis. |
|
|
//| In any case, only basis is reused. Reconstruction is performed |
|
|
//| from scratch every time you call this function. |
|
|
//| HANDLING OF DEGENERATE CASES |
|
|
//| Following degenerate cases may happen: |
|
|
//| * dataset is empty(no analysis can be done) |
|
|
//| * all sequences are shorter than the window length, no |
|
|
//| analysis can be done |
|
|
//| * no algorithm is specified(no analysis can be done) |
|
|
//| * last sequence is shorter than the window length(analysis |
|
|
//| can be done, but we can not perform reconstruction on the |
|
|
//| last sequence) |
|
|
//| Calling this function in degenerate cases returns following |
|
|
//| result: |
|
|
//| * in any case, NTicks ticks is returned |
|
|
//| * trend is assumed to be zero |
|
|
//| * noise is initialized by the last sequence; if last |
|
|
//| sequence is shorter than the window size, it is moved |
|
|
//| to the end of the array, and the beginning of the noise |
|
|
//| array is filled by zeros |
|
|
//| No analysis is performed in degenerate cases(we immediately |
|
|
//| return dummy values, no basis is constructed). |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSAAnalyzeLast(CSSAModel &s,int nticks,CRowDouble &trend,
|
|
CRowDouble &noise)
|
|
{
|
|
//--- create variables
|
|
int offs=0;
|
|
int cnt=0;
|
|
int cntzeros=0;
|
|
//--- check
|
|
if(!CAp::Assert(nticks>=1,__FUNCTION__+": NTicks<1"))
|
|
return;
|
|
//--- Init
|
|
trend.Resize(nticks);
|
|
noise.Resize(nticks);
|
|
//--- Is it degenerate case?
|
|
if(!HasSomethingToAnalyze(s) || !IsSequenceBigEnough(s,-1))
|
|
{
|
|
trend.Fill(0);
|
|
noise.Fill(0);
|
|
if(s.m_NSequences>=1)
|
|
{
|
|
cnt=MathMin(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1],nticks);
|
|
offs=s.m_SequenceIdx[s.m_NSequences]-cnt;
|
|
for(int i=0; i<cnt; i++)
|
|
noise.Set(nticks-cnt+i,s.m_SequenceData[offs+i]);
|
|
}
|
|
return;
|
|
}
|
|
//--- Fast exit: NTicks<=WindowWidth, just last window is analyzed
|
|
if(nticks<=s.m_WindowWidth)
|
|
{
|
|
SSAAnalyzeLastWindow(s,s.m_AlongTrend,s.m_AlongNoise,cnt);
|
|
offs=s.m_WindowWidth-nticks;
|
|
for(int i=0; i<nticks; i++)
|
|
{
|
|
trend.Set(i,s.m_AlongTrend[offs+i]);
|
|
noise.Set(i,s.m_AlongNoise[offs+i]);
|
|
}
|
|
return;
|
|
}
|
|
//--- Update basis.
|
|
//--- It will take care of basis validity flags. AppendLen=0 which means
|
|
//--- that we perform initial basis evaluation.
|
|
UpdateBasis(s,0,0.0);
|
|
//--- Perform analysis:
|
|
//--- * prepend max(NTicks-LastSequenceLength,0) zeros to the beginning
|
|
//--- of array
|
|
//--- * analyze the rest with AnalyzeSequence() which assumes that we
|
|
//--- already have basis
|
|
if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]>=s.m_WindowWidth,__FUNCTION__+": integrity check failed / 23vd4"))
|
|
return;
|
|
cntzeros=MathMax(nticks-(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]),0);
|
|
for(int i=0; i<cntzeros; i++)
|
|
{
|
|
trend.Set(i,0.0);
|
|
noise.Set(i,0.0);
|
|
}
|
|
cnt=MathMin(nticks,s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]);
|
|
AnalyzeSequence(s,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences]-cnt,s.m_SequenceIdx[s.m_NSequences],trend,noise,cntzeros);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function: |
|
|
//| * builds SSA basis using internally stored(entire) dataset |
|
|
//| * returns reconstruction for the sequence being passed to |
|
|
//| this function |
|
|
//| If you want to analyze last sequence stored in the model, use |
|
|
//| SSAAnalyzeLast(). |
|
|
//| Reconstruction phase involves generation of NTicks-WindowWidth |
|
|
//| sliding windows, their decomposition using empirical orthogonal |
|
|
//| functions found by SSA, followed by averaging of each data point |
|
|
//| across several overlapping windows. Thus, every point in the |
|
|
//| output trend is reconstructed using up to WindowWidth overlapping|
|
|
//| windows(WindowWidth windows exactly in the inner points, just one|
|
|
//| window at the extremal points). |
|
|
//| PERFORMANCE: this function has |
|
|
//| O((NTicks - WindowWidth)*WindowWidth*NBasis) |
|
|
//| running time. If you work in time-constrained setting and have |
|
|
//| to analyze just a few last ticks, choosing NTicks equal to |
|
|
//| WindowWidth + SmoothingLen, with SmoothingLen = 1...WindowWidth |
|
|
//| will result in good compromise between noise cancellation and |
|
|
//| analysis speed. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| Data - array[NTicks], can be larger(only NTicks leading|
|
|
//| elements will be used) |
|
|
//| NTicks - number of ticks to analyze, Nticks >= 1. |
|
|
//| * special case of NTicks<WindowWidth is handled by |
|
|
//| returning zeros as trend, and signal as noise |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Trend - array[NTicks], reconstructed trend line |
|
|
//| Noise - array[NTicks], the rest of the signal; it holds |
|
|
//| that ActualData = Trend + Noise. |
|
|
//| CACHING / REUSE OF THE BASIS |
|
|
//| Caching / reuse of previous results is performed: |
|
|
//| * first call performs full run of SSA; basis is stored in |
|
|
//| the cache |
|
|
//| * subsequent calls reuse previously cached basis |
|
|
//| * if you call any function which changes model properties |
|
|
//| (window length, algorithm, dataset), internal basis will |
|
|
//| be invalidated. |
|
|
//| * the only calls which do NOT invalidate basis are listed |
|
|
//| below: |
|
|
//| a) SSASetWindow() with same window length |
|
|
//| b) SSAAppendPointAndUpdate() |
|
|
//| c) SSAAppendSequenceAndUpdate() |
|
|
//| d) SSASetAlgoTopK...() with exactly same K |
|
|
//| Calling these functions will result in reuse of previously found |
|
|
//| basis. |
|
|
//| In any case, only basis is reused. Reconstruction is performed |
|
|
//| from scratch every time you call this function. |
|
|
//| HANDLING OF DEGENERATE CASES |
|
|
//| Following degenerate cases may happen: |
|
|
//| * dataset is empty(no analysis can be done) |
|
|
//| * all sequences are shorter than the window length, no |
|
|
//| analysis can be done |
|
|
//| * no algorithm is specified(no analysis can be done) |
|
|
//| * sequence being passed is shorter than the window length |
|
|
//| Calling this function in degenerate cases returns following |
|
|
//| result: |
|
|
//| * in any case, NTicks ticks is returned |
|
|
//| * trend is assumed to be zero |
|
|
//| * noise is initialized by the sequence. |
|
|
//| No analysis is performed in degenerate cases(we immediately |
|
|
//| return dummy values, no basis is constructed). |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSAAnalyzeSequence(CSSAModel &s,CRowDouble &data,int nticks,
|
|
CRowDouble &trend,CRowDouble &noise)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(nticks>=1,__FUNCTION__+": NTicks<1"))
|
|
return;
|
|
if(!CAp::Assert(data.Size()>=nticks,__FUNCTION__+": Data is too short"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(data,nticks),__FUNCTION__+": Data contains infinities NANs"))
|
|
return;
|
|
//--- Init
|
|
trend=vector<double>::Zeros(nticks);
|
|
noise=vector<double>::Zeros(nticks);
|
|
//--- Is it degenerate case?
|
|
if(!HasSomethingToAnalyze(s) || nticks<s.m_WindowWidth)
|
|
{
|
|
noise=data;
|
|
return;
|
|
}
|
|
//--- Update basis.
|
|
//--- It will take care of basis validity flags. AppendLen=0 which means
|
|
//--- that we perform initial basis evaluation.
|
|
UpdateBasis(s,0,0.0);
|
|
//--- Perform analysis
|
|
AnalyzeSequence(s,data,0,nticks,trend,noise,0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds SSA basis and performs forecasting for a |
|
|
//| specified number of ticks, returning value of trend. |
|
|
//| Forecast is performed as follows: |
|
|
//| * SSA trend extraction is applied to last WindowWidth |
|
|
//| elements of the internally stored dataset; this step is |
|
|
//| basically a noise reduction. |
|
|
//| * linear recurrence relation is applied to extracted trend |
|
|
//| This function has following running time: |
|
|
//| * O(NBasis*WindowWidth) for trend extraction phase (always |
|
|
//| performed) |
|
|
//| * O(WindowWidth*NTicks) for forecast phase |
|
|
//| NOTE: noise reduction is ALWAYS applied by this algorithm; if you|
|
|
//| want to apply recurrence relation to raw unprocessed data, |
|
|
//| use another function - SSAForecastSequence() which allows |
|
|
//| to turn on and off noise reduction phase. |
|
|
//| NOTE: this algorithm performs prediction using only one-last - |
|
|
//| sliding window. Predictions produced by such approach are |
|
|
//| smooth continuations of the reconstructed trend line, but |
|
|
//| they can be easily corrupted by noise. If you need noise - |
|
|
//| resistant prediction, use SSAForecastAvgLast() function, |
|
|
//| which averages predictions built using several sliding |
|
|
//| windows. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| NTicks - number of ticks to forecast, NTicks >= 1 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Trend - array[NTicks], predicted trend line |
|
|
//| CACHING / REUSE OF THE BASIS |
|
|
//| Caching / reuse of previous results is performed: |
|
|
//| * first call performs full run of SSA; basis is stored in |
|
|
//| the cache |
|
|
//| * subsequent calls reuse previously cached basis |
|
|
//| * if you call any function which changes model properties |
|
|
//| (window length, algorithm, dataset), internal basis will |
|
|
//| be invalidated. |
|
|
//| * the only calls which do NOT invalidate basis are listed |
|
|
//| below: |
|
|
//| a) SSASetWindow() with same window length |
|
|
//| b) SSAAppendPointAndUpdate() |
|
|
//| c) SSAAppendSequenceAndUpdate() |
|
|
//| d) SSASetAlgoTopK...() with exactly same K |
|
|
//| Calling these functions will result in reuse of previously found |
|
|
//| basis. |
|
|
//| HANDLING OF DEGENERATE CASES |
|
|
//| Following degenerate cases may happen: |
|
|
//| * dataset is empty(no analysis can be done) |
|
|
//| * all sequences are shorter than the window length, no |
|
|
//| analysis can be done |
|
|
//| * no algorithm is specified(no analysis can be done) |
|
|
//| * last sequence is shorter than the WindowWidth(analysis can|
|
|
//| be done, but we can not perform forecasting on the last |
|
|
//| sequence) |
|
|
//| * window lentgh is 1(impossible to use for forecasting) |
|
|
//| * SSA analysis algorithm is configured to extract basis |
|
|
//| whose size is equal to window length(impossible to use for|
|
|
//| forecasting; only basis whose size is less than window |
|
|
//| length can be used). |
|
|
//| Calling this function in degenerate cases returns following |
|
|
//| result: |
|
|
//| * NTicks copies of the last value is returned for non-empty |
|
|
//| task with large enough dataset, but with overcomplete |
|
|
//| basis (window width = 1 or basis size is equal to window |
|
|
//| width) |
|
|
//| * zero trend with length = NTicks is returned for empty task|
|
|
//| No analysis is performed in degenerate cases (we immediately |
|
|
//| return dummy values, no basis is ever constructed). |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSAForecastLast(CSSAModel &s,int nticks,CRowDouble &trend)
|
|
{
|
|
//--- create variables
|
|
int winw=s.m_WindowWidth;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(nticks>=1,__FUNCTION__+": NTicks<1"))
|
|
return;
|
|
//--- Init
|
|
trend.Resize(nticks);
|
|
//--- Is it degenerate case?
|
|
if(!HasSomethingToAnalyze(s))
|
|
{
|
|
trend.Fill(0);
|
|
return;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]<winw)
|
|
{
|
|
trend.Fill(0);
|
|
return;
|
|
}
|
|
if(winw==1)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed / 2355"))
|
|
return;
|
|
if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
for(int i=0; i<nticks; i++)
|
|
trend.Set(i,s.m_SequenceData[s.m_SequenceIdx[s.m_NSequences]-1]);
|
|
return;
|
|
}
|
|
//--- Update basis and recurrent relation.
|
|
//--- It will take care of basis validity flags. AppendLen=0 which means
|
|
//--- that we perform initial basis evaluation.
|
|
UpdateBasis(s,0,0.0);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_NBasis<=winw && s.m_NBasis>0,__FUNCTION__+": integrity check failed / 4f5et"))
|
|
return;
|
|
if(s.m_NBasis==winw)
|
|
{
|
|
//--- Handle degenerate situation with basis whose size
|
|
//--- is equal to window length.
|
|
//--- check
|
|
if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed / 2355"))
|
|
return;
|
|
if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
for(int i=0; i<nticks; i++)
|
|
trend.Set(i,s.m_SequenceData[s.m_SequenceIdx[s.m_NSequences]-1]);
|
|
return;
|
|
}
|
|
//--- Apply recurrent formula for SSA forecasting:
|
|
//--- * first, perform smoothing of the last window
|
|
//--- * second, perform analysis phase
|
|
//--- check
|
|
if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]>=s.m_WindowWidth,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
s.m_Tmp0.Resize(s.m_NBasis);
|
|
s.m_FcTrend.Resize(s.m_WindowWidth);
|
|
CAblas::RMatrixGemVect(s.m_NBasis,s.m_WindowWidth,1.0,s.m_BasisT,0,0,0,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences]-s.m_WindowWidth,0.0,s.m_Tmp0,0);
|
|
CAblas::RMatrixGemVect(s.m_WindowWidth,s.m_NBasis,1.0,s.m_Basis,0,0,0,s.m_Tmp0,0,0.0,s.m_FcTrend,0);
|
|
s.m_Tmp1.Resize(winw-1);
|
|
for(int i=1; i<winw; i++)
|
|
s.m_Tmp1.Set(i-1,s.m_FcTrend[i]);
|
|
for(int i=0; i<nticks; i++)
|
|
{
|
|
v=s.m_ForecastA[0]*s.m_Tmp1[0];
|
|
for(int j=1; j<winw-1; j++)
|
|
{
|
|
v=v+s.m_ForecastA[j]*s.m_Tmp1[j];
|
|
s.m_Tmp1.Set(j-1,s.m_Tmp1[j]);
|
|
}
|
|
trend.Set(i,v);
|
|
s.m_Tmp1.Set(winw-2,v);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds SSA basis and performs forecasting for a |
|
|
//| user - specified sequence, returning value of trend. |
|
|
//| Forecasting is done in two stages: |
|
|
//| * first, we extract trend from the WindowWidth last |
|
|
//| elements of the sequence. This stage is optional, you can |
|
|
//| turn it off if you pass data which are already processed |
|
|
//| with SSA. Of course, you can turn it off even for raw |
|
|
//| data, but it is not recommended - noise suppression is |
|
|
//| very important for correct prediction. |
|
|
//| * then, we apply LRR for last WindowWidth - 1 elements of |
|
|
//| the extracted trend. |
|
|
//| This function has following running time: |
|
|
//| * O(NBasis*WindowWidth) for trend extraction phase |
|
|
//| * O(WindowWidth*NTicks) for forecast phase |
|
|
//| NOTE: this algorithm performs prediction using only one-last - |
|
|
//| sliding window. Predictions produced by such approach are |
|
|
//| smooth continuations of the reconstructed trend line, but |
|
|
//| they can be easily corrupted by noise. If you need noise - |
|
|
//| resistant prediction, use SSAForecastAvgSequence() |
|
|
//| function, which averages predictions built using several |
|
|
//| sliding windows. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| Data - array[NTicks], data to forecast |
|
|
//| DataLen - number of ticks in the data, DataLen >= 1 |
|
|
//| ForecastLen - number of ticks to predict, ForecastLen >= 1 |
|
|
//| ApplySmoothing - whether to apply smoothing trend extraction or|
|
|
//| not; if you do not know what to specify, pass True.|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Trend - array[ForecastLen], forecasted trend |
|
|
//| CACHING / REUSE OF THE BASIS |
|
|
//| Caching / reuse of previous results is performed: |
|
|
//| * first call performs full run of SSA; basis is stored in |
|
|
//| the cache |
|
|
//| * subsequent calls reuse previously cached basis |
|
|
//| * if you call any function which changes model properties |
|
|
//| (window length, algorithm, dataset), internal basis will |
|
|
//| be invalidated. |
|
|
//| * the only calls which do NOT invalidate basis are listed |
|
|
//| below: |
|
|
//| a) SSASetWindow() with same window length |
|
|
//| b) SSAAppendPointAndUpdate() |
|
|
//| c) SSAAppendSequenceAndUpdate() |
|
|
//| d) SSASetAlgoTopK...() with exactly same K |
|
|
//| Calling these functions will result in reuse of previously found |
|
|
//| basis. |
|
|
//| HANDLING OF DEGENERATE CASES |
|
|
//| Following degenerate cases may happen: |
|
|
//| * dataset is empty(no analysis can be done) |
|
|
//| * all sequences are shorter than the window length, no |
|
|
//| analysis can be done |
|
|
//| * no algorithm is specified(no analysis can be done) |
|
|
//| * data sequence is shorter than the WindowWidth(analysis can|
|
|
//| be done, but we can not perform forecasting on the last |
|
|
//| sequence) |
|
|
//| * window lentgh is 1(impossible to use for forecasting) |
|
|
//| * SSA analysis algorithm is configured to extract basis |
|
|
//| whose size is equal to window length (impossible to use |
|
|
//| for forecasting; only basis whose size is less than window|
|
|
//| length can be used). |
|
|
//| Calling this function in degenerate cases returns following |
|
|
//| result: |
|
|
//| * ForecastLen copies of the last value is returned for |
|
|
//| non-empty task with large enough dataset, but with |
|
|
//| overcomplete basis (window width = 1 or basis size is |
|
|
//| equal to window width) |
|
|
//| * zero trend with length = ForecastLen is returned for empty|
|
|
//| task |
|
|
//| No analysis is performed in degenerate cases (we immediately |
|
|
//| return dummy values, no basis is ever constructed). |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSAForecastSequence(CSSAModel &s,CRowDouble &data,
|
|
int datalen,int forecastlen,
|
|
bool applysmoothing,
|
|
CRowDouble &trend)
|
|
{
|
|
//--- create variables
|
|
int winw=s.m_WindowWidth;
|
|
double v=0;
|
|
//--- check
|
|
if(!CAp::Assert(datalen>=1,__FUNCTION__+": DataLen<1"))
|
|
return;
|
|
if(!CAp::Assert(data.Size()>=datalen,__FUNCTION__+": Data is too short"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(data,datalen),__FUNCTION__+": Data contains infinities NANs"))
|
|
return;
|
|
if(!CAp::Assert(forecastlen>=1,__FUNCTION__+": ForecastLen<1"))
|
|
return;
|
|
//--- Init
|
|
trend.Resize(forecastlen);
|
|
//--- Is it degenerate case?
|
|
if(!HasSomethingToAnalyze(s) || datalen<winw)
|
|
{
|
|
trend.Fill(0);
|
|
return;
|
|
}
|
|
if(winw==1)
|
|
{
|
|
trend.Fill(data[datalen-1]);
|
|
return;
|
|
}
|
|
//--- Update basis.
|
|
//--- It will take care of basis validity flags. AppendLen=0 which means
|
|
//--- that we perform initial basis evaluation.
|
|
UpdateBasis(s,0,0.0);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_NBasis<=winw && s.m_NBasis>0,__FUNCTION__+": integrity check failed / 4f5et"))
|
|
return;
|
|
if(s.m_NBasis==winw)
|
|
{
|
|
//--- Handle degenerate situation with basis whose size
|
|
//--- is equal to window length.
|
|
trend.Fill(data[datalen-1]);
|
|
return;
|
|
}
|
|
//--- Perform trend extraction
|
|
CApServ::RVectorSetLengthAtLeast(s.m_FcTrend,s.m_WindowWidth);
|
|
if(applysmoothing)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(datalen>=winw,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
CApServ::RVectorSetLengthAtLeast(s.m_Tmp0,s.m_NBasis);
|
|
CAblas::RMatrixGemVect(s.m_NBasis,winw,1.0,s.m_BasisT,0,0,0,data,datalen-winw,0.0,s.m_Tmp0,0);
|
|
CAblas::RMatrixGemVect(winw,s.m_NBasis,1.0,s.m_Basis,0,0,0,s.m_Tmp0,0,0.0,s.m_FcTrend,0);
|
|
}
|
|
else
|
|
{
|
|
for(int i=0; i<winw; i++)
|
|
s.m_FcTrend.Set(i,data[datalen+i-winw]);
|
|
}
|
|
//--- Apply recurrent formula for SSA forecasting
|
|
CApServ::RVectorSetLengthAtLeast(s.m_Tmp1,winw-1);
|
|
for(int i=1; i<winw; i++)
|
|
s.m_Tmp1.Set(i-1,s.m_FcTrend[i]);
|
|
for(int i=0; i<forecastlen; i++)
|
|
{
|
|
v=s.m_ForecastA[0]*s.m_Tmp1[0];
|
|
for(int j=1; j<=winw-2; j++)
|
|
{
|
|
v=v+s.m_ForecastA[j]*s.m_Tmp1[j];
|
|
s.m_Tmp1.Set(j-1,s.m_Tmp1[j]);
|
|
}
|
|
trend.Set(i,v);
|
|
s.m_Tmp1.Set(winw-2,v);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds SSA basis and performs forecasting for a |
|
|
//| specified number of ticks, returning value of trend. |
|
|
//| Forecast is performed as follows: |
|
|
//| * SSA trend extraction is applied to last M sliding windows|
|
|
//| of the internally stored dataset |
|
|
//| * for each of M sliding windows, M predictions are built |
|
|
//| * average value of M predictions is returned |
|
|
//| This function has following running time: |
|
|
//| * O(NBasis*WindowWidth*M) for trend extraction phase |
|
|
//| (always performed) |
|
|
//| * O(WindowWidth*NTicks*M) for forecast phase |
|
|
//| NOTE: noise reduction is ALWAYS applied by this algorithm; if you|
|
|
//| want to apply recurrence relation to raw unprocessed data, |
|
|
//| use another function - SSAForecastSequence() which allows |
|
|
//| to turn on and off noise reduction phase. |
|
|
//| NOTE: combination of several predictions results in lesser |
|
|
//| sensitivity to noise, but it may produce undesirable |
|
|
//| discontinuities between last point of the trend and first |
|
|
//| point of the prediction. The reason is that last point of |
|
|
//| the trend is usually corrupted by noise, but average value |
|
|
//| of several predictions is less sensitive to noise, thus |
|
|
//| discontinuity appears. It is not a bug. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| M - number of sliding windows to combine, M >= 1. If |
|
|
//| your dataset has less than M sliding windows, this |
|
|
//| parameter will be silently reduced. |
|
|
//| NTicks - number of ticks to forecast, NTicks >= 1 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Trend - array[NTicks], predicted trend line |
|
|
//| CACHING / REUSE OF THE BASIS |
|
|
//| Caching / reuse of previous results is performed: |
|
|
//| * first call performs full run of SSA; basis is stored in |
|
|
//| the cache |
|
|
//| * subsequent calls reuse previously cached basis |
|
|
//| * if you call any function which changes model properties |
|
|
//| (window length, algorithm, dataset), internal basis will |
|
|
//| be invalidated. |
|
|
//| * the only calls which do NOT invalidate basis are listed |
|
|
//| below: |
|
|
//| a) SSASetWindow() with same window length |
|
|
//| b) SSAAppendPointAndUpdate() |
|
|
//| c) SSAAppendSequenceAndUpdate() |
|
|
//| d) SSASetAlgoTopK...() with exactly same K |
|
|
//| Calling these functions will result in reuse of previously found |
|
|
//| basis. |
|
|
//| HANDLING OF DEGENERATE CASES |
|
|
//| Following degenerate cases may happen: |
|
|
//| * dataset is empty(no analysis can be done) |
|
|
//| * all sequences are shorter than the window length, no |
|
|
//| analysis can be done |
|
|
//| * no algorithm is specified(no analysis can be done) |
|
|
//| * last sequence is shorter than the WindowWidth(analysis can|
|
|
//| be done, but we can not perform forecasting on the last |
|
|
//| sequence) |
|
|
//| * window lentgh is 1(impossible to use for forecasting) |
|
|
//| * SSA analysis algorithm is configured to extract basis |
|
|
//| whose size is equal to window length(impossible to use for|
|
|
//| forecasting; only basis whose size is less than window |
|
|
//| length can be used). |
|
|
//| Calling this function in degenerate cases returns following |
|
|
//| result: |
|
|
//| * NTicks copies of the last value is returned for non-empty |
|
|
//| task with large enough dataset, but with overcomplete basis |
|
|
//| (window width = 1 or basis size is equal to window width) |
|
|
//| * zero trend with length = NTicks is returned for empty task |
|
|
//| No analysis is performed in degenerate cases (we immediately |
|
|
//| return dummy values, no basis is ever constructed). |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSAForecastAvgLast(CSSAModel &s,int m,int nticks,
|
|
CRowDouble &trend)
|
|
{
|
|
int winw=s.m_WindowWidth;
|
|
//--- check
|
|
if(!CAp::Assert(nticks>=1,__FUNCTION__+": NTicks<1"))
|
|
return;
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1"))
|
|
return;
|
|
//--- Init
|
|
trend.Resize(nticks);
|
|
//--- Is it degenerate case?
|
|
if(!HasSomethingToAnalyze(s))
|
|
{
|
|
trend.Fill(0);
|
|
return;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
if(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]<winw)
|
|
{
|
|
trend.Fill(0);
|
|
return;
|
|
}
|
|
if(winw==1)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed / 2355"))
|
|
return;
|
|
if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
trend.Fill(s.m_SequenceData[s.m_SequenceIdx[s.m_NSequences]-1]);
|
|
return;
|
|
}
|
|
//--- Update basis and recurrent relation.
|
|
//--- It will take care of basis validity flags. AppendLen=0 which means
|
|
//--- that we perform initial basis evaluation.
|
|
UpdateBasis(s,0,0.0);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_NBasis<=winw && s.m_NBasis>0,__FUNCTION__+": integrity check failed / 4f5et"))
|
|
return;
|
|
if(s.m_NBasis==winw)
|
|
{
|
|
//--- Handle degenerate situation with basis whose size
|
|
//--- is equal to window length.
|
|
//--- check
|
|
if(!CAp::Assert(s.m_NSequences>0,__FUNCTION__+": integrity check failed / 2355"))
|
|
return;
|
|
if(!CAp::Assert(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]>0,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
trend.Fill(s.m_SequenceData[s.m_SequenceIdx[s.m_NSequences]-1]);
|
|
return;
|
|
}
|
|
//--- Decrease M if we have less than M sliding windows.
|
|
//--- Forecast.
|
|
m=MathMin(m,s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]-winw+1);
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
ForecastAvgSequence(s,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences-1],s.m_SequenceIdx[s.m_NSequences],m,nticks,true,trend,0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function builds SSA basis and performs forecasting for a |
|
|
//| user - specified sequence, returning value of trend. |
|
|
//| Forecasting is done in two stages: |
|
|
//| * first, we extract trend from M last sliding windows of the |
|
|
//| sequence. This stage is optional, you can turn it off i you |
|
|
//| pass data which are already processed with SSA. Of course, |
|
|
//| you can turn it off even for raw data, but it is not |
|
|
//| recommended - noise suppression is very important for correct|
|
|
//| prediction. |
|
|
//| * then, we apply LRR independently for M sliding windows |
|
|
//| * average of M predictions is returned |
|
|
//| This function has following running time: |
|
|
//| * O(NBasis*WindowWidth*M) for trend extraction phase |
|
|
//| * O(WindowWidth*NTicks*M) for forecast phase |
|
|
//| NOTE: combination of several predictions results in lesser |
|
|
//| sensitivity to noise, but it may produce undesirable |
|
|
//| discontinuities between last point of the trend and first |
|
|
//| point of the prediction. The reason is that last point of |
|
|
//| the trend is usually corrupted by noise, but average value |
|
|
//| of several predictions is less sensitive to noise, thus |
|
|
//| discontinuity appears. It is not a bug. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - SSA model |
|
|
//| Data - array[NTicks], data to forecast |
|
|
//| DataLen - number of ticks in the data, DataLen >= 1 |
|
|
//| M - number of sliding windows to combine, M >= 1. |
|
|
//| If your dataset has less than M sliding windows,|
|
|
//| this parameter will be silently reduced. |
|
|
//| ForecastLen - number of ticks to predict, ForecastLen >= 1 |
|
|
//| ApplySmoothing - whether to apply smoothing trend extraction |
|
|
//| or not. if you do not know what to specify, |
|
|
//| pass true. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Trend - array[ForecastLen], forecasted trend |
|
|
//| CACHING / REUSE OF THE BASIS |
|
|
//| Caching / reuse of previous results is performed: |
|
|
//| * first call performs full run of SSA; basis is stored in |
|
|
//| the cache |
|
|
//| * subsequent calls reuse previously cached basis |
|
|
//| * if you call any function which changes model properties |
|
|
//| (window length, algorithm, dataset), internal basis will be |
|
|
//| invalidated. |
|
|
//| * the only calls which do NOT invalidate basis are listed |
|
|
//| below: |
|
|
//| a) SSASetWindow() with same window length |
|
|
//| b) SSAAppendPointAndUpdate() |
|
|
//| c) SSAAppendSequenceAndUpdate() |
|
|
//| d) SSASetAlgoTopK...() with exactly same K |
|
|
//| Calling these functions will result in reuse of previously found |
|
|
//| basis. |
|
|
//| HANDLING OF DEGENERATE CASES |
|
|
//| Following degenerate cases may happen: |
|
|
//| * dataset is empty(no analysis can be done) |
|
|
//| * all sequences are shorter than the window length, no analysis|
|
|
//| can be done |
|
|
//| * no algorithm is specified(no analysis can be done) |
|
|
//| * data sequence is shorter than the WindowWidth (analysis can |
|
|
//| be done, but we can not perform forecasting on the last |
|
|
//| sequence) |
|
|
//| * window lentgh is 1 (impossible to use for forecasting) |
|
|
//| * SSA analysis algorithm is configured to extract basis whose |
|
|
//| size is equal to window length (impossible to use for |
|
|
//| forecasting; only basis whose size is less than window length|
|
|
//| can be used). |
|
|
//| Calling this function in degenerate cases returns following |
|
|
//| result: |
|
|
//| * ForecastLen copies of the last value is returned for |
|
|
//| non-empty task with large enough dataset, but with |
|
|
//| overcomplete basis (window width = 1 or basis size |
|
|
//| is equal to window width) |
|
|
//| * zero trend with length = ForecastLen is returned for |
|
|
//| empty task |
|
|
//| No analysis is performed in degenerate case s(we immediately |
|
|
//| return dummy values, no basis is ever constructed). |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::SSAForecastAvgSequence(CSSAModel &s,CRowDouble &data,
|
|
int datalen,int m,
|
|
int forecastlen,bool applysmoothing,
|
|
CRowDouble &trend)
|
|
{
|
|
int winw=s.m_WindowWidth;
|
|
//--- check
|
|
if(!CAp::Assert(datalen>=1,__FUNCTION__+": DataLen<1"))
|
|
return;
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": M<1"))
|
|
return;
|
|
if(!CAp::Assert(data.Size()>=datalen,__FUNCTION__+": Data is too short"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteVector(data,datalen),__FUNCTION__+": Data contains infinities NANs"))
|
|
return;
|
|
if(!CAp::Assert(forecastlen>=1,__FUNCTION__+": ForecastLen<1"))
|
|
return;
|
|
//--- Init
|
|
trend.Resize(forecastlen);
|
|
//--- Is it degenerate case?
|
|
if(!HasSomethingToAnalyze(s) || datalen<winw)
|
|
{
|
|
trend.Fill(0);
|
|
return;
|
|
}
|
|
if(winw==1)
|
|
{
|
|
trend.Fill(data[datalen-1]);
|
|
return;
|
|
}
|
|
//--- Update basis.
|
|
//--- It will take care of basis validity flags. AppendLen=0 which means
|
|
//--- that we perform initial basis evaluation.
|
|
UpdateBasis(s,0,0.0);
|
|
//--- check
|
|
if(!CAp::Assert(s.m_NBasis<=winw && s.m_NBasis>0,__FUNCTION__+": integrity check failed / 4f5et"))
|
|
return;
|
|
if(s.m_NBasis==winw)
|
|
{
|
|
//--- Handle degenerate situation with basis whose size
|
|
//--- is equal to window length.
|
|
trend.Fill(data[datalen-1]);
|
|
return;
|
|
}
|
|
//--- Decrease M if we have less than M sliding windows.
|
|
//--- Forecast.
|
|
m=MathMin(m,datalen-winw+1);
|
|
//--- check
|
|
if(!CAp::Assert(m>=1,__FUNCTION__+": integrity check failed"))
|
|
return;
|
|
ForecastAvgSequence(s,data,0,datalen,m,forecastlen,applysmoothing,trend,0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function evaluates current model and tells whether we have |
|
|
//| some data which can be analyzed by current algorithm, or not. |
|
|
//| No analysis can be done in the following degenerate cases: |
|
|
//| * dataset is empty |
|
|
//| * all sequences are shorter than the window length |
|
|
//| * no algorithm is specified |
|
|
//+------------------------------------------------------------------+
|
|
bool CSSA::HasSomethingToAnalyze(CSSAModel &s)
|
|
{
|
|
//--- create variables
|
|
bool result;
|
|
bool allsmaller=true;
|
|
bool isdegenerate=false;
|
|
//--- initialization
|
|
isdegenerate=isdegenerate || s.m_AlgoType==0;
|
|
isdegenerate=isdegenerate || s.m_NSequences==0;
|
|
//---
|
|
for(int i=0; i<s.m_NSequences; i++)
|
|
allsmaller=allsmaller && s.m_SequenceIdx[i+1]-s.m_SequenceIdx[i]<s.m_WindowWidth;
|
|
isdegenerate=isdegenerate || allsmaller;
|
|
result=!isdegenerate;
|
|
//--- return result
|
|
return(result);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function checks whether I-th sequence is big enough for |
|
|
//| analysis or not. |
|
|
//| I = -1 is used to denote last sequence(for NSequences = 0) |
|
|
//+------------------------------------------------------------------+
|
|
bool CSSA::IsSequenceBigEnough(CSSAModel &s,int i)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(i>=-1 && i<s.m_NSequences))
|
|
return(false);
|
|
if(s.m_NSequences==0)
|
|
return(false);
|
|
//---
|
|
if(i<0)
|
|
i=s.m_NSequences-1;
|
|
//--- return result
|
|
return (s.m_SequenceIdx[i+1]-s.m_SequenceIdx[i]>=s.m_WindowWidth);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs basis update. Either full update |
|
|
//| (recalculated from the very beginning) or partial update |
|
|
//| (handles append to the end of the dataset). |
|
|
//| With AppendLen = 0 this function behaves as follows: |
|
|
//| * if AreBasisAndSolverValid = False, then solver object is |
|
|
//| created from scratch, initial calculations are performed |
|
|
//| according to specific SSA algorithm being chosen. Basis / |
|
|
//| Solver validity flag is set to True, then we immediately |
|
|
//| return. |
|
|
//| * if AreBasisAndSolverValid = True, then nothing is done - we |
|
|
//| immediately return. |
|
|
//| With AppendLen > 0 this function behaves as follows: |
|
|
//| * if AreBasisAndSolverValid = False, then exception is |
|
|
//| generated; you can append points only to fully constructed |
|
|
//| basis. Call this function with zero AppendLen BEFORE append, |
|
|
//| then perform append, then call it one more time with non-zero|
|
|
//| AppendLen. |
|
|
//| * if AreBasisAndSolverValid = True, then basis is incrementally|
|
|
//| updated. It also updates recurrence relation used for |
|
|
//| prediction. It is expected that either AppendLen = 1, or |
|
|
//| AppendLen = length (last_sequence). Basis update is |
|
|
//| performed with probability UpdateIts (larger-than-one values |
|
|
//| mean that some amount of iterations is always performed). |
|
|
//| In any case, after calling this function we either: |
|
|
//| * have an exception |
|
|
//| * have completely valid basis |
|
|
//| IMPORTANT: this function expects that we do NOT call it for |
|
|
//| degenerate tasks (no data). So, call it after check |
|
|
//| with HasSomethingToAnalyze() returned True. |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::UpdateBasis(CSSAModel &s,int appendlen,double updateits)
|
|
{
|
|
//--- create variables
|
|
int i=0;
|
|
int j=0;
|
|
int k=0;
|
|
int srcoffs=0;
|
|
int dstoffs=0;
|
|
int winw=s.m_WindowWidth;
|
|
int windowstotal=0;
|
|
int requesttype=0;
|
|
int requestsize=0;
|
|
double v=0;
|
|
bool degeneraterecurrence=false;
|
|
double nu2=0;
|
|
int subspaceits=0;
|
|
bool needevd=false;
|
|
//--- Critical checks
|
|
if(!CAp::Assert(appendlen>=0,__FUNCTION__+": incorrect parameters passed to UpdateBasis(),integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(!(!s.m_AreBasisAndSolverValid && appendlen!=0),__FUNCTION__+": incorrect parameters passed to UpdateBasis(),integrity check failed"))
|
|
return;
|
|
if(!CAp::Assert(!(appendlen==0 && updateits>0.0),__FUNCTION__+": incorrect parameters passed to UpdateBasis(),integrity check failed"))
|
|
return;
|
|
//--- Everything is OK, nothing to do
|
|
if(s.m_AreBasisAndSolverValid && appendlen==0)
|
|
return;
|
|
//--- Seed RNG with fixed or random seed.
|
|
//--- RNG used when pseudorandomly deciding whether
|
|
//--- to re-evaluate basis or not. Sandom seed is
|
|
//--- important when we have several simultaneously
|
|
//--- calculated SSA models - we do not want them
|
|
//--- to be re-evaluated in same moments).
|
|
if(!s.m_AreBasisAndSolverValid)
|
|
{
|
|
if(s.m_RngSeed>0)
|
|
CHighQualityRand::HQRndSeed(s.m_RngSeed,s.m_RngSeed+235,s.m_RS);
|
|
else
|
|
CHighQualityRand::HQRndRandomize(s.m_RS);
|
|
}
|
|
//--- Compute XXT for algorithms which need it
|
|
if(!s.m_AreBasisAndSolverValid)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(appendlen==0,__FUNCTION__+": integrity check failed / 34cx6"))
|
|
return;
|
|
switch(s.m_AlgoType)
|
|
{
|
|
case 2:
|
|
//--- Compute X*X^T for direct algorithm.
|
|
//--- Quite straightforward, no subtle optimizations.
|
|
s.m_XXT=matrix<double>::Zeros(winw,winw);
|
|
windowstotal=0;
|
|
for(i=0; i<s.m_NSequences; i++)
|
|
windowstotal+=MathMax(s.m_SequenceIdx[i+1]-s.m_SequenceIdx[i]-winw+1,0);
|
|
//--- check
|
|
if(!CAp::Assert(windowstotal>0,__FUNCTION__+": integrity check in UpdateBasis() failed / 76t34"))
|
|
return;
|
|
UpdateXXTPrepare(s,windowstotal,winw,s.m_MemoryLimit);
|
|
for(i=0; i<s.m_NSequences; i++)
|
|
{
|
|
for(j=0; j<MathMax(s.m_SequenceIdx[i+1]-s.m_SequenceIdx[i]-winw+1,0); j++)
|
|
UpdateXXTSend(s,s.m_SequenceData,s.m_SequenceIdx[i]+j,s.m_XXT);
|
|
}
|
|
UpdateXXTFinalize(s,s.m_XXT);
|
|
break;
|
|
case 3:
|
|
//--- Compute X*X^T for real-time algorithm:
|
|
//--- * prepare queue of windows to merge into XXT
|
|
//--- * shuffle queue in order to avoid time-related biases in algorithm
|
|
//--- * dequeue first chunk
|
|
s.m_XXT.Resize(winw,winw);
|
|
windowstotal=0;
|
|
for(i=0; i<s.m_NSequences; i++)
|
|
windowstotal+=MathMax(s.m_SequenceIdx[i+1]-s.m_SequenceIdx[i]-winw+1,0);
|
|
//--- check
|
|
if(!CAp::Assert(windowstotal>0,__FUNCTION__+": integrity check in UpdateBasis() failed / 76t34"))
|
|
return;
|
|
CApServ::IVectorSetLengthAtLeast(s.m_Rtqueue,windowstotal);
|
|
dstoffs=0;
|
|
for(i=0; i<s.m_NSequences; i++)
|
|
for(j=0; j<MathMax(s.m_SequenceIdx[i+1]-s.m_SequenceIdx[i]-winw+1,0); j++)
|
|
{
|
|
srcoffs=s.m_SequenceIdx[i]+j;
|
|
s.m_Rtqueue.Set(dstoffs,srcoffs);
|
|
dstoffs++;
|
|
}
|
|
//--- check
|
|
if(!CAp::Assert(dstoffs==windowstotal,__FUNCTION__+": integrity check in UpdateBasis() failed / fh45f"))
|
|
return;
|
|
if(s.m_RtPowerUp>1)
|
|
//--- Shuffle queue, it helps to avoid time-related bias in algorithm
|
|
for(i=0; i<windowstotal; i++)
|
|
{
|
|
j=i+CHighQualityRand::HQRndUniformI(s.m_RS,windowstotal-i);
|
|
s.m_Rtqueue.Swap(i,j);
|
|
}
|
|
s.m_RtqueueCnt=windowstotal;
|
|
s.m_RtqueueChunk=1;
|
|
s.m_RtqueueChunk=MathMax(s.m_RtqueueChunk,s.m_RtqueueCnt/s.m_RtPowerUp);
|
|
s.m_RtqueueChunk=MathMax(s.m_RtqueueChunk,2*s.m_TopK);
|
|
RealtimeDequeue(s,0.0,MathMin(s.m_RtqueueChunk,s.m_RtqueueCnt));
|
|
break;
|
|
}
|
|
}
|
|
//--- Handle possible updates for XXT:
|
|
//--- * check that append involves either last point of last sequence,
|
|
//--- or entire last sequence
|
|
//--- * if last sequence is shorter than window width, perform quick exit -
|
|
//--- we have nothing to update - no windows to insert into XXT
|
|
//--- * update XXT
|
|
if(appendlen>0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_AreBasisAndSolverValid,__FUNCTION__+": integrity check failed / 5gvz3"))
|
|
return;
|
|
if(!CAp::Assert(s.m_NSequences>=1,__FUNCTION__+": integrity check failed / 658ev"))
|
|
return;
|
|
if(!CAp::Assert(appendlen==1 || appendlen==s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]-winw+1,__FUNCTION__+": integrity check failed / sd3g7"))
|
|
return;
|
|
if(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]<winw)
|
|
//--- Last sequence is too short, nothing to update
|
|
return;
|
|
if(s.m_AlgoType==2 || s.m_AlgoType==3)
|
|
{
|
|
if(appendlen>1)
|
|
{
|
|
//--- Long append, use GEMM for updates
|
|
UpdateXXTPrepare(s,appendlen,winw,s.m_MemoryLimit);
|
|
for(j=0; j<MathMax(s.m_SequenceIdx[s.m_NSequences]-s.m_SequenceIdx[s.m_NSequences-1]-winw+1,0); j++)
|
|
UpdateXXTSend(s,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences-1]+j,s.m_XXT);
|
|
UpdateXXTFinalize(s,s.m_XXT);
|
|
}
|
|
else
|
|
{
|
|
//--- Just one element is added, use rank-1 update
|
|
CAblas::RMatrixGer(winw,winw,s.m_XXT,0,0,1.0,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences]-winw,s.m_SequenceData,s.m_SequenceIdx[s.m_NSequences]-winw);
|
|
}
|
|
}
|
|
}
|
|
//--- Now, perform basis calculation - either full recalculation (AppendLen=0)
|
|
//--- or quick update (AppendLen>0).
|
|
switch(s.m_AlgoType)
|
|
{
|
|
case 1:
|
|
//--- Precomputed basis
|
|
if(winw!=s.m_PrecomputedWidth)
|
|
{
|
|
//--- Window width has changed, reset basis to zeros
|
|
s.m_NBasis=1;
|
|
s.m_Basis-matrix<double>::Zeros(winw,1);
|
|
s.m_SV=vector<double>::Zeros(1);
|
|
}
|
|
else
|
|
{
|
|
//--- OK, use precomputed basis
|
|
s.m_NBasis=s.m_PrecomputedNBasis;
|
|
s.m_Basis=s.m_PrecomputedBasis;
|
|
s.m_SV=vector<double>::Zeros(s.m_NBasis);
|
|
}
|
|
s.m_BasisT=s.m_Basis.Transpose()+0;
|
|
break;
|
|
case 2:
|
|
//--- Direct top-K algorithm
|
|
//--- Calculate eigenvectors with SMatrixEVD(), reorder by descending
|
|
//--- of magnitudes.
|
|
//--- Update is performed for invalid basis or for non-zero UpdateIts.
|
|
needevd=!s.m_AreBasisAndSolverValid;
|
|
needevd=needevd || updateits>=1.0;
|
|
needevd=needevd || CHighQualityRand::HQRndUniformR(s.m_RS)<(double)(updateits-(int)MathFloor(updateits));
|
|
if(needevd)
|
|
{
|
|
s.m_DbgCntEVD++;
|
|
s.m_NBasis=MathMin(winw,s.m_TopK);
|
|
if(!CEigenVDetect::SMatrixEVD(s.m_XXT,winw,1,true,s.m_SV,s.m_Basis))
|
|
{
|
|
CAp::Assert(false,__FUNCTION__+": SMatrixEVD failed");
|
|
return;
|
|
}
|
|
for(i=0; i<winw; i++)
|
|
{
|
|
k=winw-1-i;
|
|
if(i>=k)
|
|
break;
|
|
s.m_SV.Swap(i,k);
|
|
s.m_Basis.SwapCols(i,k);
|
|
}
|
|
for(i=0; i<=s.m_NBasis-1; i++)
|
|
s.m_SV.Set(i,MathSqrt(MathMax(s.m_SV[i],0.0)));
|
|
s.m_BasisT=s.m_Basis.Transpose()+0;
|
|
}
|
|
break;
|
|
case 3:
|
|
//--- Real-time top-K.
|
|
//--- Determine actual number of basis components, prepare subspace
|
|
//--- solver (either create from scratch or reuse).
|
|
//--- Update is always performed for invalid basis; for a valid basis
|
|
//--- it is performed with probability UpdateIts.
|
|
if(s.m_RtPowerUp==1)
|
|
subspaceits=s.m_DefaultSubspaceits;
|
|
else
|
|
subspaceits=3;
|
|
if(appendlen>0)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_AreBasisAndSolverValid,__FUNCTION__+": integrity check in UpdateBasis() failed / srg6f"))
|
|
return;
|
|
if(!CAp::Assert(updateits>=0.0,__FUNCTION__+": integrity check in UpdateBasis() failed / srg4f"))
|
|
return;
|
|
subspaceits=(int)MathFloor(updateits);
|
|
if(CHighQualityRand::HQRndUniformR(s.m_RS)<(double)(updateits-(int)MathFloor(updateits)))
|
|
subspaceits++;
|
|
//--- check
|
|
if(!CAp::Assert(subspaceits>=0,__FUNCTION__+": integrity check in UpdateBasis() failed / srg9f"))
|
|
return;
|
|
//--- Dequeue pending dataset and merge it into XXT.
|
|
//--- Dequeuing is done only for appends, and only when we have
|
|
//--- non-empty queue.
|
|
if(s.m_RtqueueCnt>0)
|
|
RealtimeDequeue(s,1.0,MathMin(s.m_RtqueueChunk,s.m_RtqueueCnt));
|
|
}
|
|
//--- Now, proceed to solver
|
|
if(subspaceits>0)
|
|
{
|
|
if(appendlen==0)
|
|
{
|
|
s.m_NBasis=MathMin(winw,s.m_TopK);
|
|
CEigenVDetect::EigSubSpaceCreateBuf(winw,s.m_NBasis,s.m_Solver);
|
|
}
|
|
else
|
|
CEigenVDetect::EigSubSpaceSetWarmStart(s.m_Solver,true);
|
|
CEigenVDetect::EigSubSpaceSetCond(s.m_Solver,0.0,subspaceits);
|
|
//--- Perform initial basis estimation
|
|
s.m_DbgCntEVD++;
|
|
CEigenVDetect::EigSubSpaceOOCStart(s.m_Solver,0);
|
|
while(CEigenVDetect::EigSubSpaceOOCContinue(s.m_Solver))
|
|
{
|
|
CEigenVDetect::EigSubSpaceOOCGetRequestInfo(s.m_Solver,requesttype,requestsize);
|
|
//--- check
|
|
if(!CAp::Assert(requesttype==0,__FUNCTION__+": integrity check in UpdateBasis() failed / 346372"))
|
|
return;
|
|
CAblas::RMatrixGemm(winw,requestsize,winw,1.0,s.m_XXT,0,0,0,s.m_Solver.m_X,0,0,0,0.0,s.m_Solver.m_AX,0,0);
|
|
}
|
|
CEigenVDetect::EigSubSpaceOOCStop(s.m_Solver,s.m_SV,s.m_Basis,s.m_SolverRep);
|
|
s.m_SV.Clip(0,DBL_MAX);
|
|
s.m_SV=s.m_SV.Sqrt()+0;
|
|
s.m_BasisT=s.m_Basis.Transpose()+0;
|
|
}
|
|
break;
|
|
default:
|
|
CAp::Assert(false,__FUNCTION__+": integrity check in UpdateBasis() failed / dfgs34");
|
|
break;
|
|
}
|
|
//--- Update recurrent relation
|
|
CApServ::RVectorSetLengthAtLeast(s.m_ForecastA,MathMax(winw-1,1));
|
|
degeneraterecurrence=false;
|
|
if(winw>1)
|
|
{
|
|
//--- Non-degenerate case
|
|
s.m_Tmp0=s.m_BasisT.Col(winw-1)+0;
|
|
s.m_Tmp0.Resize(s.m_NBasis);
|
|
nu2=(s.m_Tmp0.Pow(2.0)+0).Sum();
|
|
if(nu2<(1.0-1000*CMath::m_machineepsilon))
|
|
CAblas::RMatrixGemVect(winw-1,s.m_NBasis,1/(1-nu2),s.m_BasisT,0,0,1,s.m_Tmp0,0,0.0,s.m_ForecastA,0);
|
|
else
|
|
degeneraterecurrence=true;
|
|
}
|
|
else
|
|
degeneraterecurrence=true;
|
|
if(degeneraterecurrence)
|
|
{
|
|
s.m_ForecastA.Fill(0.0);
|
|
s.m_ForecastA.Set(MathMax(winw-2,0),1.0);
|
|
}
|
|
//--- Set validity flag
|
|
s.m_AreBasisAndSolverValid=true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs analysis using current basis. It assumes |
|
|
//| and checks that validity flag AreBasisAndSolverValid is set. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model |
|
|
//| Data - array which holds data in elements [I0, I1): |
|
|
//| * right bound is not included. |
|
|
//| * I1 - I0 >= WindowWidth(assertion is performed). |
|
|
//| Trend - preallocated output array, large enough |
|
|
//| Noise - preallocated output array, large enough |
|
|
//| Offs - offset in Trend / Noise where result is stored; |
|
|
//| I1 - I0 elements are written starting at offset |
|
|
//| Offs. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Trend, Noise - processing results |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::AnalyzeSequence(CSSAModel &s,CRowDouble &data,
|
|
int i0,int i1,
|
|
CRowDouble &trend,
|
|
CRowDouble &noise,
|
|
int offs)
|
|
{
|
|
//--- create variables
|
|
int winw=0;
|
|
int nwindows=0;
|
|
int cnt=0;
|
|
int batchstart=0;
|
|
int batchlimit=0;
|
|
int batchsize=0;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_AreBasisAndSolverValid,__FUNCTION__+": integrity check failed / d84sz0"))
|
|
return;
|
|
if(!CAp::Assert(i1-i0>=s.m_WindowWidth,__FUNCTION__+": integrity check failed / d84sz1"))
|
|
return;
|
|
if(!CAp::Assert(s.m_NBasis>=1,__FUNCTION__+": integrity check failed / d84sz2"))
|
|
return;
|
|
//--- initialization
|
|
nwindows=i1-i0-s.m_WindowWidth+1;
|
|
winw=s.m_WindowWidth;
|
|
batchlimit=MathMax(nwindows,1);
|
|
if(s.m_MemoryLimit>0)
|
|
batchlimit=MathMin(batchlimit,MathMax(s.m_MemoryLimit/winw,4*winw));
|
|
//--- Zero-initialize trend and counts
|
|
cnt=i1-i0;
|
|
CApServ::IVectorSetLengthAtLeast(s.m_AseqCounts,cnt);
|
|
s.m_AseqCounts.Fill(0,0,cnt);
|
|
for(int i=0; i<cnt; i++)
|
|
trend.Set(offs+i,0.0);
|
|
//--- Reset temporaries if algorithm settings changed since last round
|
|
if(s.m_AseqTrajectory.Cols()!=winw)
|
|
s.m_AseqTrajectory.Resize(0,0);
|
|
if(s.m_AseqTbProduct.Cols()!=s.m_NBasis)
|
|
s.m_AseqTbProduct.Resize(0,0);
|
|
//--- Perform batch processing
|
|
CApServ::RMatrixSetLengthAtLeast(s.m_AseqTrajectory,batchlimit,winw);
|
|
CApServ::RMatrixSetLengthAtLeast(s.m_AseqTbProduct,batchlimit,s.m_NBasis);
|
|
batchsize=0;
|
|
batchstart=offs;
|
|
for(int i=0; i<nwindows; i++)
|
|
{
|
|
//--- Enqueue next row of trajectory matrix
|
|
if(batchsize==0)
|
|
batchstart=i;
|
|
for(int j=0; j<winw; j++)
|
|
s.m_AseqTrajectory.Set(batchsize,j,data[i0+i+j]);
|
|
batchsize++;
|
|
//--- Process batch
|
|
if(batchsize==batchlimit || i==nwindows-1)
|
|
{
|
|
//--- Project onto basis
|
|
CAblas::RMatrixGemm(batchsize,s.m_NBasis,winw,1.0,s.m_AseqTrajectory,0,0,0,s.m_BasisT,0,0,1,0.0,s.m_AseqTbProduct,0,0);
|
|
CAblas::RMatrixGemm(batchsize,winw,s.m_NBasis,1.0,s.m_AseqTbProduct,0,0,0,s.m_BasisT,0,0,0,0.0,s.m_AseqTrajectory,0,0);
|
|
//--- Hankelize
|
|
for(int k=0; k<batchsize; k++)
|
|
for(int j=0; j<winw; j++)
|
|
{
|
|
trend.Add(offs+batchstart+k+j,s.m_AseqTrajectory.Get(k,j));
|
|
s.m_AseqCounts.Set(batchstart+k+j,s.m_AseqCounts[batchstart+k+j]+1);
|
|
}
|
|
//--- Reset batch size
|
|
batchsize=0;
|
|
}
|
|
}
|
|
for(int i=0; i<cnt; i++)
|
|
{
|
|
trend.Mul(offs+i,1.0/s.m_AseqCounts[i]);
|
|
noise.Set(offs+i,data[i0+i]-trend[offs+i]);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function performs averaged forecasting. It assumes that |
|
|
//| basis is already built, everything is valid and checked. See |
|
|
//| comments on similar public functions to find out more about |
|
|
//| averaged predictions. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model |
|
|
//| Data - array which holds data in elements [I0, I1): |
|
|
//| * right bound is not included. |
|
|
//| * I1 - I0 >= WindowWidth(assertion is performed)|
|
|
//| M - number of sliding windows to combine, M >= 1. |
|
|
//| If your dataset has less than M sliding windows,|
|
|
//| this parameter will be silently reduced. |
|
|
//| ForecastLen - number of ticks to predict, ForecastLen >= 1 |
|
|
//| Trend - preallocated output array, large enough |
|
|
//| Offs - offset in Trend where result is stored; |
|
|
//| I1 - I0 elements are written starting at offset |
|
|
//| Offs. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Trend - array[ForecastLen], forecasted trend |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::ForecastAvgSequence(CSSAModel &s,CRowDouble &data,
|
|
int i0,int i1,int m,
|
|
int forecastlen,
|
|
bool smooth,
|
|
CRowDouble &trend,
|
|
int offs)
|
|
{
|
|
int winw=s.m_WindowWidth;
|
|
//--- check
|
|
if(!CAp::Assert(s.m_AreBasisAndSolverValid,__FUNCTION__+": integrity check failed / d84sz0"))
|
|
return;
|
|
if(!CAp::Assert(i1-i0-s.m_WindowWidth+1>=m,__FUNCTION__+": integrity check failed / d84sz1"))
|
|
return;
|
|
if(!CAp::Assert(s.m_NBasis>=1,__FUNCTION__+": integrity check failed / d84sz2"))
|
|
return;
|
|
if(!CAp::Assert(s.m_WindowWidth>=2,__FUNCTION__+": integrity check failed / 5tgdg5"))
|
|
return;
|
|
if(!CAp::Assert(s.m_WindowWidth>s.m_NBasis,__FUNCTION__+": integrity check failed / d5g56w"))
|
|
return;
|
|
//--- Prepare M synchronized predictions for the last known tick
|
|
//--- (last one is an actual value of the trend, previous M-1 predictions
|
|
//--- are predictions from differently positioned sliding windows).
|
|
CApServ::RMatrixSetLengthAtLeast(s.m_FcTrendM,m,winw);
|
|
CApServ::RVectorSetLengthAtLeast(s.m_Tmp0,MathMax(m,s.m_NBasis));
|
|
CApServ::RVectorSetLengthAtLeast(s.m_Tmp1,winw);
|
|
for(int k=0; k<m; k++)
|
|
{
|
|
//--- Perform prediction for rows [0,K-1]
|
|
CAblas::RMatrixGemVect(k,winw-1,1.0,s.m_FcTrendM,0,1,0,s.m_ForecastA,0,0.0,s.m_Tmp0,0);
|
|
for(int i=0; i<k; i++)
|
|
{
|
|
for(int j=1; j<winw; j++)
|
|
s.m_FcTrendM.Set(i,j-1,s.m_FcTrendM.Get(i,j));
|
|
s.m_FcTrendM.Set(i,winw-1,s.m_Tmp0[i]);
|
|
}
|
|
//--- Perform trend extraction for row K, add it to dataset
|
|
if(smooth)
|
|
{
|
|
CAblas::RMatrixGemVect(s.m_NBasis,winw,1.0,s.m_BasisT,0,0,0,data,i1-winw-(m-1-k),0.0,s.m_Tmp0,0);
|
|
CAblas::RMatrixGemVect(s.m_WindowWidth,s.m_NBasis,1.0,s.m_Basis,0,0,0,s.m_Tmp0,0,0.0,s.m_Tmp1,0);
|
|
if(s.m_FcTrendM.Cols()==s.m_Tmp1.Size())
|
|
s.m_FcTrendM.Row(k,s.m_Tmp1);
|
|
else
|
|
for(int j=0; j<winw; j++)
|
|
s.m_FcTrendM.Set(k,j,s.m_Tmp1[j]);
|
|
}
|
|
else
|
|
for(int j=0; j<winw; j++)
|
|
s.m_FcTrendM.Set(k,j,data[i1-winw-(m-1-k)+j]);
|
|
}
|
|
//--- Now we have M synchronized predictions of the sequence state at the last
|
|
//--- know moment (last "prediction" is just a copy of the trend). Let's start
|
|
//--- batch prediction!
|
|
for(int k=0; k<forecastlen; k++)
|
|
{
|
|
CAblas::RMatrixGemVect(m,winw-1,1.0,s.m_FcTrendM,0,1,0,s.m_ForecastA,0,0.0,s.m_Tmp0,0);
|
|
trend.Set(offs+k,0.0);
|
|
for(int i=0; i<=m-1; i++)
|
|
{
|
|
for(int j=1; j<winw; j++)
|
|
s.m_FcTrendM.Set(i,j-1,s.m_FcTrendM.Get(i,j));
|
|
s.m_FcTrendM.Set(i,winw-1,s.m_Tmp0[i]);
|
|
trend.Add(offs+k,s.m_Tmp0[i]);
|
|
}
|
|
trend.Mul(offs+k,1.0/m);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function extracts updates from real-time queue and applies |
|
|
//| them to the S.XXT matrix. XXT is premultiplied by Beta, which can|
|
|
//| be 0.0 for initial creation, 1.0 for subsequent updates, or even |
|
|
//| within(0, 1) for some kind of updates with decay. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model |
|
|
//| Beta - >= 0, coefficient to premultiply XXT |
|
|
//| Cnt - 0 < Cnt <= S.RTQueueCnt, number of updates |
|
|
//| to extract from the end of the queue |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - S.XXT updated, S.RTQueueCnt decreased |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::RealtimeDequeue(CSSAModel &s,double beta,int cnt)
|
|
{
|
|
int winw=s.m_WindowWidth;
|
|
//--- check
|
|
if(!CAp::Assert(cnt>0,__FUNCTION__+": RealTimeDequeue() integrity check failed / 43tdv"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(beta) && beta>=0.0,__FUNCTION__+": RealTimeDequeue() integrity check failed / 5gdg6"))
|
|
return;
|
|
if(!CAp::Assert(cnt<=s.m_RtqueueCnt,__FUNCTION__+": RealTimeDequeue() integrity check failed / 547yh"))
|
|
return;
|
|
if(!CAp::Assert(s.m_XXT.Cols()>=s.m_WindowWidth,__FUNCTION__+": RealTimeDequeue() integrity check failed / 54bf4"))
|
|
return;
|
|
if(!CAp::Assert(s.m_XXT.Rows()>=s.m_WindowWidth,__FUNCTION__+": RealTimeDequeue() integrity check failed / 9gdfn"))
|
|
return;
|
|
//--- Premultiply XXT by Beta
|
|
if(beta!=0.0)
|
|
s.m_XXT*=beta;
|
|
else
|
|
s.m_XXT.Fill(0,winw,winw);
|
|
//--- Dequeue
|
|
UpdateXXTPrepare(s,cnt,winw,s.m_MemoryLimit);
|
|
for(int i=0; i<cnt; i++)
|
|
{
|
|
UpdateXXTSend(s,s.m_SequenceData,s.m_Rtqueue[s.m_RtqueueCnt-1],s.m_XXT);
|
|
s.m_RtqueueCnt--;
|
|
}
|
|
UpdateXXTFinalize(s,s.m_XXT);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function prepares batch buffer for XXT update. The idea is |
|
|
//| that we send a stream of "XXT += u*u'" updates, and we want to |
|
|
//| package them into one big matrix update U*U', applied with SYRK()|
|
|
//| kernel, but U can consume too much memory, so we want to |
|
|
//| transparently divide it into few smaller chunks. |
|
|
//| This set of functions solves this problem: |
|
|
//| * UpdateXXTPrepare() prepares temporary buffers |
|
|
//| * UpdateXXTSend() sends next u to the buffer, |
|
|
//| possibly initiating next SYRK() |
|
|
//| * UpdateXXTFinalize() performs last SYRK() update |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model, only fields with UX prefix are used |
|
|
//| UpdateSize - number of updates |
|
|
//| WindowWidth - window width, > 0 |
|
|
//| MemoryLimit - memory limit, non-positive value means no limit |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - UX temporaries updated |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::UpdateXXTPrepare(CSSAModel &s,int updatesize,int windowwidth,
|
|
int memorylimit)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(windowwidth>0,__FUNCTION__+": WinW<=0"))
|
|
return;
|
|
|
|
s.m_UxBatchLimit=MathMax(updatesize,1);
|
|
if(memorylimit>0)
|
|
s.m_UxBatchLimit=MathMin(s.m_UxBatchLimit,MathMax(memorylimit/windowwidth,4*windowwidth));
|
|
s.m_UxBatchWidth=windowwidth;
|
|
s.m_UxBatchSize=0;
|
|
if(s.m_UxBatch.Cols()!=windowwidth)
|
|
s.m_UxBatch.Resize(0,0);
|
|
CApServ::RMatrixSetLengthAtLeast(s.m_UxBatch,s.m_UxBatchLimit,windowwidth);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sends update u*u' to the batch buffer. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model, only fields with UX prefix are used |
|
|
//| U - WindowWidth - sized update, starts at I0 |
|
|
//| I0 - starting position for update |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - UX temporaries updated |
|
|
//| XXT - array[WindowWidth, WindowWidth], in the middle |
|
|
//| of update. All intermediate updates are applied |
|
|
//| to the upper triangle. |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::UpdateXXTSend(CSSAModel &s,CRowDouble &u,int i0,
|
|
CMatrixDouble &xxt)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(i0+s.m_UxBatchWidth-1<u.Size(),__FUNCTION__+": incorrect U size"))
|
|
return;
|
|
if(!CAp::Assert(s.m_UxBatchSize>=0,__FUNCTION__+": integrity check failure"))
|
|
return;
|
|
if(!CAp::Assert(s.m_UxBatchSize<=s.m_UxBatchLimit,__FUNCTION__+": integrity check failure"))
|
|
return;
|
|
if(!CAp::Assert(s.m_UxBatchLimit>=1,__FUNCTION__+": integrity check failure"))
|
|
return;
|
|
//--- Send pending batch if full
|
|
if(s.m_UxBatchSize==s.m_UxBatchLimit)
|
|
{
|
|
CAblas::RMatrixSyrk(s.m_UxBatchWidth,s.m_UxBatchSize,1.0,s.m_UxBatch,0,0,2,1.0,xxt,0,0,true);
|
|
s.m_UxBatchSize=0;
|
|
}
|
|
//--- Append update to batch
|
|
int i1_=i0;
|
|
for(int i_=0; i_<s.m_UxBatchWidth; i_++)
|
|
s.m_UxBatch.Set(s.m_UxBatchSize,i_,u[i_+i1_]);
|
|
s.m_UxBatchSize++;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function finalizes batch buffer. Call it after the last |
|
|
//| update. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - model, only fields with UX prefix are used |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - UX temporaries updated |
|
|
//| XXT - array[WindowWidth, WindowWidth], updated with |
|
|
//| all previous updates, both triangles of the |
|
|
//| symmetric matrix are present. |
|
|
//+------------------------------------------------------------------+
|
|
void CSSA::UpdateXXTFinalize(CSSAModel &s,CMatrixDouble &xxt)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(s.m_UxBatchSize>=0,__FUNCTION__+": integrity check failure"))
|
|
return;
|
|
if(!CAp::Assert(s.m_UxBatchSize<=s.m_UxBatchLimit,__FUNCTION__+": integrity check failure"))
|
|
return;
|
|
if(!CAp::Assert(s.m_UxBatchLimit>=1,__FUNCTION__+": integrity check failure"))
|
|
return;
|
|
//---
|
|
if(s.m_UxBatchSize>0)
|
|
{
|
|
CAblas::RMatrixSyrk(s.m_UxBatchWidth,s.m_UxBatchSize,1.0,s.m_UxBatch,0,0,2,1.0,s.m_XXT,0,0,true);
|
|
s.m_UxBatchSize=0;
|
|
}
|
|
CAblas::RMatrixEnforceSymmetricity(s.m_XXT,s.m_UxBatchWidth,true);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Buffer object which is used to perform various requests (usually |
|
|
//| model inference) in the multithreaded mode (multiple threads |
|
|
//| working with same KNN object). |
|
|
//| This object should be created with KNNCreateBuffer(). |
|
|
//+------------------------------------------------------------------+
|
|
struct CKNNBuffer
|
|
{
|
|
CKDTreeRequestBuffer m_treebuf;
|
|
CRowDouble m_x;
|
|
CRowDouble m_y;
|
|
CRowInt m_tags;
|
|
CMatrixDouble m_xy;
|
|
//---
|
|
CKNNBuffer(void) {}
|
|
~CKNNBuffer(void) {}
|
|
void Copy(const CKNNBuffer &obj);
|
|
//--- overloading
|
|
void operator=(const CKNNBuffer &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CKNNBuffer::Copy(const CKNNBuffer &obj)
|
|
{
|
|
m_treebuf=obj.m_treebuf;
|
|
m_x=obj.m_x;
|
|
m_y=obj.m_y;
|
|
m_tags=obj.m_tags;
|
|
m_xy=obj.m_xy;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| A KNN builder object; this object encapsulates dataset and all |
|
|
//| related settings, it is used to create an actual instance of KNN |
|
|
//| model. |
|
|
//+------------------------------------------------------------------+
|
|
struct CKNNBuilder
|
|
{
|
|
int m_dstype;
|
|
int m_npoints;
|
|
int m_nvars;
|
|
bool m_iscls;
|
|
int m_nout;
|
|
CMatrixDouble m_dsdata;
|
|
CRowDouble m_dsrval;
|
|
CRowInt m_dsival;
|
|
int m_knnnrm;
|
|
//---
|
|
CKNNBuilder(void);
|
|
~CKNNBuilder(void) {}
|
|
void Copy(const CKNNBuilder &obj);
|
|
//--- overloading
|
|
void operator=(const CKNNBuilder &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CKNNBuilder::CKNNBuilder(void)
|
|
{
|
|
m_dstype=0;
|
|
m_npoints=0;
|
|
m_nvars=0;
|
|
m_iscls=false;
|
|
m_nout=0;
|
|
m_knnnrm=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CKNNBuilder::Copy(const CKNNBuilder &obj)
|
|
{
|
|
m_dstype=obj.m_dstype;
|
|
m_npoints=obj.m_npoints;
|
|
m_nvars=obj.m_nvars;
|
|
m_iscls=obj.m_iscls;
|
|
m_nout=obj.m_nout;
|
|
m_dsdata=obj.m_dsdata;
|
|
m_dsrval=obj.m_dsrval;
|
|
m_dsival=obj.m_dsival;
|
|
m_knnnrm=obj.m_knnnrm;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| KNN model, can be used for classification or regression |
|
|
//+------------------------------------------------------------------+
|
|
struct CKNNModel
|
|
{
|
|
int m_nvars;
|
|
int m_nout;
|
|
int m_k;
|
|
double m_eps;
|
|
bool m_iscls;
|
|
bool m_isdummy;
|
|
CKDTree m_tree;
|
|
CKNNBuffer m_buffer;
|
|
//---
|
|
CKNNModel(void);
|
|
~CKNNModel(void) {}
|
|
//---
|
|
void Copy(const CKNNModel &obj);
|
|
//--- overloading
|
|
void operator=(const CKNNModel &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CKNNModel::CKNNModel(void)
|
|
{
|
|
m_nvars=0;
|
|
m_nout=0;
|
|
m_k=0;
|
|
m_eps=0;
|
|
m_iscls=false;
|
|
m_isdummy=false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CKNNModel::Copy(const CKNNModel &obj)
|
|
{
|
|
m_nvars=obj.m_nvars;
|
|
m_nout=obj.m_nout;
|
|
m_k=obj.m_k;
|
|
m_eps=obj.m_eps;
|
|
m_iscls=obj.m_iscls;
|
|
m_isdummy=obj.m_isdummy;
|
|
m_tree=obj.m_tree;
|
|
m_buffer=obj.m_buffer;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| KNN training report. |
|
|
//| Following fields store training set errors: |
|
|
//| * relclserror - fraction of misclassified cases, [0,1] |
|
|
//| * avgce - average cross-entropy in bits per symbol |
|
|
//| * rmserror - root-mean-square error |
|
|
//| * avgerror - average error |
|
|
//| * avgrelerror - average relative error |
|
|
//| For classification problems: |
|
|
//| * RMS, AVG and AVGREL errors are calculated for posterior |
|
|
//| probabilities |
|
|
//| For regression problems: |
|
|
//| * RELCLS and AVGCE errors are zero |
|
|
//+------------------------------------------------------------------+
|
|
struct CKNNReport
|
|
{
|
|
double m_RelCLSError;
|
|
double m_AvgCE;
|
|
double m_RMSError;
|
|
double m_AvgError;
|
|
double m_AvgRelError;
|
|
//---
|
|
CKNNReport(void) { ZeroMemory(this); }
|
|
~CKNNReport(void) {}
|
|
//---
|
|
void Copy(const CKNNReport &obj);
|
|
//--- overloading
|
|
void operator=(const CKNNReport &obj) { Copy(obj); }
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CKNNReport::Copy(const CKNNReport &obj)
|
|
{
|
|
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;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
class CKNN
|
|
{
|
|
public:
|
|
//--- constant
|
|
static const int m_knnfirstversion;
|
|
|
|
static void KNNCreateBuffer(CKNNModel &model,CKNNBuffer &buf);
|
|
static void KNNBuilderCreate(CKNNBuilder &s);
|
|
static void KNNBuilderSetDatasetReg(CKNNBuilder &s,CMatrixDouble &xy,int npoints,int nvars,int nout);
|
|
static void KNNBuilderSetDatasetCLS(CKNNBuilder &s,CMatrixDouble &xy,int npoints,int nvars,int nclasses);
|
|
static void KNNBuilderSetNorm(CKNNBuilder &s,int nrmtype);
|
|
static void KNNBuilderBuildKNNModel(CKNNBuilder &s,int k,double eps,CKNNModel &model,CKNNReport &rep);
|
|
static void KNNRewriteKEps(CKNNModel &model,int k,double eps);
|
|
static void KNNProcess(CKNNModel &model,CRowDouble &x,CRowDouble &y);
|
|
static double KNNProcess0(CKNNModel &model,CRowDouble &x);
|
|
static int KNNClassify(CKNNModel &model,CRowDouble &x);
|
|
static void KNNProcessI(CKNNModel &model,CRowDouble &x,CRowDouble &y);
|
|
static void KNNTsProcess(CKNNModel &model,CKNNBuffer &buf,CRowDouble &x,CRowDouble &y);
|
|
static double KNNRelClsError(CKNNModel &model,CMatrixDouble &xy,int npoints);
|
|
static double KNNAvgCE(CKNNModel &model,CMatrixDouble &xy,int npoints);
|
|
static double KNNRMSError(CKNNModel &model,CMatrixDouble &xy,int npoints);
|
|
static double KNNAvgError(CKNNModel &model,CMatrixDouble &xy,int npoints);
|
|
static double CKNN::KNNAvgRelError(CKNNModel &model,CMatrixDouble &xy,int npoints);
|
|
static void KNNAllErrors(CKNNModel &model,CMatrixDouble &xy,int npoints,CKNNReport &rep);
|
|
static void CKNN::KNNAlloc(CSerializer &s,CKNNModel &model);
|
|
static void KNNSerialize(CSerializer &s,CKNNModel &model);
|
|
static void KNNUnserialize(CSerializer &s,CKNNModel &model);
|
|
|
|
private:
|
|
static void ClearReport(CKNNReport &rep);
|
|
static void ProcessInternal(CKNNModel &model,CKNNBuffer &buf);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
const int CKNN::m_knnfirstversion=0;
|
|
//+------------------------------------------------------------------+
|
|
//| This function creates buffer structure which can be used to |
|
|
//| perform parallel KNN requests. |
|
|
//| KNN subpackage provides two sets of computing functions - ones |
|
|
//| which use internal buffer of KNN model (these functions are |
|
|
//| single-threaded because they use same buffer, which can not |
|
|
//| shared between threads), and ones which use external buffer. |
|
|
//| This function is used to initialize external buffer. |
|
|
//| INPUT PARAMETERS |
|
|
//| Model - KNN model which is associated with newly created|
|
|
//| buffer |
|
|
//| OUTPUT PARAMETERS |
|
|
//| Buf - external buffer. |
|
|
//| IMPORTANT: buffer object should be used only with model which was|
|
|
//| used to initialize buffer. Any attempt to use buffer|
|
|
//| with different object is dangerous - you may get |
|
|
//| integrity check failure (exception) because sizes of |
|
|
//| internal arrays do not fit to dimensions of the model |
|
|
//| structure. |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNCreateBuffer(CKNNModel &model,CKNNBuffer &buf)
|
|
{
|
|
if(!model.m_isdummy)
|
|
CNearestNeighbor::KDTreeCreateRequestBuffer(model.m_tree,buf.m_treebuf);
|
|
buf.m_x.Resize(model.m_nvars);
|
|
buf.m_y.Resize(model.m_nout);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine creates KNNBuilder object which is used to train |
|
|
//| KNN models. |
|
|
//| By default, new builder stores empty dataset and some reasonable |
|
|
//| default settings. At the very least, you should specify dataset |
|
|
//| prior to building KNN model. You can also tweak settings of the |
|
|
//| model construction algorithm (recommended, although default |
|
|
//| settings should work well). |
|
|
//| Following actions are mandatory: |
|
|
//| * calling knnbuildersetdataset() to specify dataset |
|
|
//| * calling knnbuilderbuildknnmodel() to build KNN model using |
|
|
//| current dataset and default settings |
|
|
//| Additionally, you may call: |
|
|
//| * KNNBuilderSetNorm() to change norm being used |
|
|
//| INPUT PARAMETERS: |
|
|
//| none |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - KNN builder |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNBuilderCreate(CKNNBuilder &s)
|
|
{
|
|
//--- Empty dataset
|
|
s.m_dstype=-1;
|
|
s.m_npoints=0;
|
|
s.m_nvars=0;
|
|
s.m_iscls=false;
|
|
s.m_nout=1;
|
|
//--- Default training settings
|
|
s.m_knnnrm=2;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Specifies regression problem (one or more continuous output |
|
|
//| variables are predicted). There also exists "classification" |
|
|
//| version of this function. |
|
|
//| This subroutine adds dense dataset to the internal storage of the|
|
|
//| builder object. Specifying your dataset in the dense format means|
|
|
//| that the dense version of the KNN construction algorithm will be |
|
|
//| invoked. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - KNN builder object |
|
|
//| XY - array[NPoints,NVars+NOut] (note: actual size can|
|
|
//| be larger, only leading part is used anyway), |
|
|
//| dataset: |
|
|
//| * first NVars elements of each row store values |
|
|
//| of the independent variables |
|
|
//| * next NOut elements store values of the |
|
|
//| dependent variables |
|
|
//| NPoints - number of rows in the dataset, NPoints>=1 |
|
|
//| NVars - number of independent variables, NVars>=1 |
|
|
//| NOut - number of dependent variables, NOut>=1 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - KNN builder |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNBuilderSetDatasetReg(CKNNBuilder &s,CMatrixDouble &xy,
|
|
int npoints,int nvars,int nout)
|
|
{
|
|
//--- Check parameters
|
|
if(!CAp::Assert(npoints>=1,__FUNCTION__+": npoints<1"))
|
|
return;
|
|
if(!CAp::Assert(nvars>=1,__FUNCTION__+": nvars<1"))
|
|
return;
|
|
if(!CAp::Assert(nout>=1,__FUNCTION__+": nout<1"))
|
|
return;
|
|
if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__+": rows(xy)<npoints"))
|
|
return;
|
|
if(!CAp::Assert(xy.Cols()>=nvars+nout,__FUNCTION__+": cols(xy)<nvars+nout"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nvars+nout),__FUNCTION__+": xy parameter contains INFs or NANs"))
|
|
return;
|
|
//--- Set dataset
|
|
s.m_dstype=0;
|
|
s.m_iscls=false;
|
|
s.m_npoints=npoints;
|
|
s.m_nvars=nvars;
|
|
s.m_nout=nout;
|
|
ulong parts[]={nvars,nout};
|
|
matrix<double> splited[];
|
|
xy.Split(parts,1,splited);
|
|
s.m_dsdata=splited[0];
|
|
splited[1].Reshape(1,npoints*nout);
|
|
s.m_dsrval=splited[1].Row(0);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Specifies classification problem (two or more classes are |
|
|
//| predicted). There also exists "regression" version of this |
|
|
//| function. |
|
|
//| This subroutine adds dense dataset to the internal storage of the|
|
|
//| builder object. Specifying your dataset in the dense format means|
|
|
//| that the dense version of the KNN construction algorithm will be |
|
|
//| invoked. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - KNN builder object |
|
|
//| XY - array[NPoints, NVars + 1] (note: actual size can be|
|
|
//| larger, only leading part is used anyway), dataset:|
|
|
//| * first NVars elements of each row store values of |
|
|
//| the independent variables |
|
|
//| * next element stores class index, in [0, NClasses)|
|
|
//| NPoints - number of rows in the dataset, NPoints >= 1 |
|
|
//| NVars - number of independent variables, NVars >= 1 |
|
|
//| NClasses - number of classes, NClasses >= 2 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - KNN builder |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNBuilderSetDatasetCLS(CKNNBuilder &s,
|
|
CMatrixDouble &xy,
|
|
int npoints,int nvars,int nclasses)
|
|
{
|
|
//--- Check parameters
|
|
if(!CAp::Assert(npoints>=1,__FUNCTION__+": npoints<1"))
|
|
return;
|
|
if(!CAp::Assert(nvars>=1,__FUNCTION__+": nvars<1"))
|
|
return;
|
|
if(!CAp::Assert(nclasses>=2,__FUNCTION__+": nclasses<2"))
|
|
return;
|
|
if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__+": rows(xy)<npoints"))
|
|
return;
|
|
if(!CAp::Assert(xy.Cols()>=nvars+1,__FUNCTION__+": cols(xy)<nvars+1"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nvars+1),__FUNCTION__+": xy parameter contains INFs or NANs"))
|
|
return;
|
|
vector<double> y=xy.Col(nvars);
|
|
if(!CAp::Assert(y.Min()>=0 && y.Max()<nclasses,__FUNCTION__+": last column of xy contains invalid class number"))
|
|
return;
|
|
//--- Set dataset
|
|
s.m_iscls= true;
|
|
s.m_dstype= 0;
|
|
s.m_npoints= npoints;
|
|
s.m_nvars= nvars;
|
|
s.m_nout= nclasses;
|
|
ulong parts[]={nvars,nclasses};
|
|
matrix<double> splited[];
|
|
xy.Split(parts,1,splited);
|
|
s.m_dsdata=splited[0];
|
|
s.m_dsival.Resize(npoints);
|
|
for(int i=0; i<npoints; i++)
|
|
s.m_dsival.Set(i,(int)MathRound(y[i]));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function sets norm type used for neighbor search. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - decision forest builder object |
|
|
//| NormType - norm type: |
|
|
//| * 0 inf-norm |
|
|
//| * 1 1-norm |
|
|
//| * 2 Euclidean norm(default) |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| S - decision forest builder |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNBuilderSetNorm(CKNNBuilder &s,int nrmtype)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(nrmtype==0 || nrmtype==1 || nrmtype==2,__FUNCTION__+": unexpected norm type"))
|
|
return;
|
|
s.m_knnnrm=nrmtype;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This subroutine builds KNN model according to current settings, |
|
|
//| using dataset internally stored in the builder object. |
|
|
//| The model being built performs inference using Eps-approximate |
|
|
//| K nearest neighbors search algorithm, with: |
|
|
//| *K=1, Eps=0 corresponding to the "nearest neighbor |
|
|
//| algorithm" |
|
|
//| *K>1, Eps=0 corresponding to the "K nearest neighbors |
|
|
//| algorithm" |
|
|
//| *K>=1, Eps>0 corresponding to "approximate nearest |
|
|
//| neighbors algorithm" |
|
|
//| An approximate KNN is a good option for high-dimensional datasets|
|
|
//| (exact KNN works slowly when dimensions count grows). |
|
|
//| An ALGLIB implementation of kd-trees is used to perform k-nn |
|
|
//| searches. |
|
|
//| INPUT PARAMETERS: |
|
|
//| S - KNN builder object |
|
|
//| K - number of neighbors to search for, K >= 1 |
|
|
//| Eps - approximation factor: |
|
|
//| * Eps = 0 means that exact kNN search is performed |
|
|
//| * Eps > 0 means that(1 + Eps) - approximate search |
|
|
//| is performed |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Model - KNN model |
|
|
//| Rep - report |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNBuilderBuildKNNModel(CKNNBuilder &s,int k,double eps,
|
|
CKNNModel &model,CKNNReport &rep)
|
|
{
|
|
//--- create variables
|
|
int npoints=s.m_npoints;
|
|
int nvars=s.m_nvars;
|
|
int nout=s.m_nout;
|
|
bool iscls=s.m_iscls;
|
|
CMatrixDouble xy;
|
|
CRowInt tags;
|
|
//--- Check settings
|
|
if(!CAp::Assert(k>=1,__FUNCTION__+": k<1"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(eps) && eps>=0.0,__FUNCTION__+": eps<0"))
|
|
return;
|
|
//--- Prepare output
|
|
ClearReport(rep);
|
|
model.m_nvars=nvars;
|
|
model.m_nout=nout;
|
|
model.m_iscls=iscls;
|
|
model.m_k=k;
|
|
model.m_eps=eps;
|
|
model.m_isdummy=false;
|
|
//--- Quick exit for empty dataset
|
|
if(s.m_dstype==-1)
|
|
{
|
|
model.m_isdummy=true;
|
|
return;
|
|
}
|
|
//--- Build kd-tree
|
|
if(iscls)
|
|
{
|
|
xy=s.m_dsdata;
|
|
xy.Resize(npoints,nvars+1);
|
|
xy.Col(nvars,s.m_dsival);
|
|
tags=s.m_dsival;
|
|
CNearestNeighbor::KDTreeBuildTagged(xy,tags,npoints,nvars,0,s.m_knnnrm,model.m_tree);
|
|
}
|
|
else
|
|
{
|
|
xy=s.m_dsdata;
|
|
xy.Resize(npoints,nvars+nout);
|
|
for(int i=0; i<npoints; i++)
|
|
for(int j=0; j<nout; j++)
|
|
xy.Set(i,nvars+j,s.m_dsrval[i*nout+j]);
|
|
CNearestNeighbor::KDTreeBuild(xy,npoints,nvars,nout,s.m_knnnrm,model.m_tree);
|
|
}
|
|
//--- Build buffer
|
|
KNNCreateBuffer(model,model.m_buffer);
|
|
//--- Report
|
|
KNNAllErrors(model,xy,npoints,rep);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Changing search settings of KNN model. |
|
|
//| K and EPS parameters of KNN(AKNN) search are specified during |
|
|
//| model construction. However, plain KNN algorithm with Euclidean |
|
|
//| distance allows you to change them at any moment. |
|
|
//| NOTE: future versions of KNN model may support advanced versions |
|
|
//| of KNN, such as NCA or LMNN. It is possible that such |
|
|
//| algorithms won't allow you to change search settings on the|
|
|
//| fly. If you call this function for an algorithm which does |
|
|
//| not support on-the-fly changes, it will throw an exception|
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - KNN model |
|
|
//| K - K >= 1, neighbors count |
|
|
//| EPS - accuracy of the EPS-approximate NN search. Set |
|
|
//| to 0.0, if you want to perform "classic" KNN |
|
|
//| search. Specify larger values if you need to |
|
|
//| speed-up high-dimensional KNN queries. |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| nothing on success, exception on failure |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNRewriteKEps(CKNNModel &model,int k,double eps)
|
|
{
|
|
//--- check
|
|
if(!CAp::Assert(k>=1,__FUNCTION__+": k<1"))
|
|
return;
|
|
if(!CAp::Assert(MathIsValidNumber(eps) && eps>=0.0,__FUNCTION__+": eps<0"))
|
|
return;
|
|
//--- change values
|
|
model.m_k=k;
|
|
model.m_eps=eps;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Inference using KNN model. |
|
|
//| See also KNNProcess0(), KNNProcessI() and KNNClassify() for |
|
|
//| options with a bit more convenient interface. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - KNN model |
|
|
//| X - input vector, array[0..NVars - 1]. |
|
|
//| Y - possible preallocated buffer. Reused if long enough|
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - result. Regression estimate when solving regression|
|
|
//| task, vector of posterior probabilities for |
|
|
//| classification task. |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNProcess(CKNNModel &model,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
KNNTsProcess(model,model.m_buffer,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns first component of the inferred vector |
|
|
//| (i.e.one with index #0). |
|
|
//| It is a convenience wrapper for KNNProcess() intended for either:|
|
|
//| * 1 - dimensional regression problems |
|
|
//| * 2 - class classification problems |
|
|
//| In the former case this function returns inference result as |
|
|
//| scalar, which is definitely more convenient that wrapping it as |
|
|
//| vector. In the latter case it returns probability of object |
|
|
//| belonging to class #0. |
|
|
//| If you call it for anything different from two cases above, it |
|
|
//| will work as defined, i.e. return y[0], although it is of less |
|
|
//| use in such cases. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - KNN model |
|
|
//| X - input vector, array[0..NVars - 1]. |
|
|
//| RESULT: |
|
|
//| Y[0] |
|
|
//+------------------------------------------------------------------+
|
|
double CKNN::KNNProcess0(CKNNModel &model,CRowDouble &x)
|
|
{
|
|
//--- copy
|
|
model.m_buffer.m_x=x;
|
|
//--- function call
|
|
ProcessInternal(model,model.m_buffer);
|
|
//--- return result
|
|
return(model.m_buffer.m_y[0]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function returns most probable class number for an input X.|
|
|
//| It is same as calling KNNProcess(model, x, y), then determining |
|
|
//| i = argmax(y[i]) and returning i. |
|
|
//| A class number in [0, NOut) range in returned for classification |
|
|
//| problems, -1 is returned when this function is called for |
|
|
//| regression problems. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - KNN model |
|
|
//| X - input vector, array[0..NVars - 1]. |
|
|
//| RESULT: |
|
|
//| class number, -1 for regression tasks |
|
|
//+------------------------------------------------------------------+
|
|
int CKNN::KNNClassify(CKNNModel &model,CRowDouble &x)
|
|
{
|
|
//--- check
|
|
if(!model.m_iscls)
|
|
return (-1);
|
|
//--- function call
|
|
ProcessInternal(model,model.m_buffer);
|
|
//--- return result
|
|
return ((int)model.m_buffer.m_y.ArgMax());
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| 'interactive' variant of KNNProcess() which support constructs |
|
|
//| like "y = KNNProcessI(model,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. |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNProcessI(CKNNModel &model,CRowDouble &x,CRowDouble &y)
|
|
{
|
|
y.Resize(0);
|
|
KNNProcess(model,x,y);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Thread - safe procesing using external buffer for temporaries. |
|
|
//| This function is thread-safe(i.e. you can use same KNN model from|
|
|
//| multiple threads) as long as you use different buffer objects for|
|
|
//| different threads. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - KNN model |
|
|
//| Buf - buffer object, must be allocated specifically for |
|
|
//| this model with KNNCreateBuffer(). |
|
|
//| X - input vector, array[NVars] |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Y - result, array[NOut]. Regression estimate when |
|
|
//| solving regression task, vector of posterior |
|
|
//| probabilities for a classification task. |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNTsProcess(CKNNModel &model,CKNNBuffer &buf,CRowDouble &x,
|
|
CRowDouble &y)
|
|
{
|
|
int nout=model.m_nout;
|
|
//--- copy
|
|
buf.m_x=x;
|
|
ProcessInternal(model,buf);
|
|
//--- return result
|
|
if(y.Size()<=nout)
|
|
y=buf.m_y;
|
|
else
|
|
for(int i=0;i<nout;i++)
|
|
y.Set(i,buf.m_y[i]);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Relative classification error on the test set |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - KNN model |
|
|
//| XY - test set |
|
|
//| NPoints - test set size |
|
|
//| RESULT: |
|
|
//| percent of incorrectly classified cases. |
|
|
//| Zero if model solves regression task. |
|
|
//| NOTE: if you need several different kinds of error metrics, it is|
|
|
//| better to use KNNAllErrors() which computes all error |
|
|
//| metric with just one pass over dataset. |
|
|
//+------------------------------------------------------------------+
|
|
double CKNN::KNNRelClsError(CKNNModel &model,CMatrixDouble &xy,int npoints)
|
|
{
|
|
CKNNReport rep;
|
|
//--- function call
|
|
KNNAllErrors(model,xy,npoints,rep);
|
|
//--- return result
|
|
return(rep.m_RelCLSError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Average cross-entropy (in bits per element) on the test set |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - KNN model |
|
|
//| XY - test set |
|
|
//| NPoints - test set size |
|
|
//| RESULT: |
|
|
//| CrossEntropy / NPoints. |
|
|
//| Zero if model solves regression task. |
|
|
//| NOTE: the cross-entropy metric is too unstable when used |
|
|
//| to evaluate KNN models (such models can report exactly |
|
|
//| zero probabilities), so we do not recommend using it. |
|
|
//| NOTE: if you need several different kinds of error metrics, it |
|
|
//| is better to use KNNAllErrors() which computes all error |
|
|
//| metric with just one pass over dataset. |
|
|
//+------------------------------------------------------------------+
|
|
double CKNN::KNNAvgCE(CKNNModel &model,CMatrixDouble &xy,int npoints)
|
|
{
|
|
CKNNReport rep;
|
|
//--- function call
|
|
KNNAllErrors(model,xy,npoints,rep);
|
|
//--- return result
|
|
return(rep.m_AvgCE);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| RMS error on the test set. |
|
|
//| Its meaning for regression task is obvious. As for classification|
|
|
//| problems, RMS error means error when estimating posterior |
|
|
//| probabilities. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - KNN model |
|
|
//| XY - test set |
|
|
//| NPoints - test set size |
|
|
//| RESULT: |
|
|
//| root mean square error. |
|
|
//| NOTE: if you need several different kinds of error metrics, it |
|
|
//| is better to use KNNAllErrors() which computes all error |
|
|
//| metric with just one pass over dataset. |
|
|
//+------------------------------------------------------------------+
|
|
double CKNN::KNNRMSError(CKNNModel &model,CMatrixDouble &xy,int npoints)
|
|
{
|
|
CKNNReport rep;
|
|
//--- function call
|
|
KNNAllErrors(model,xy,npoints,rep);
|
|
//--- return result
|
|
return(rep.m_RMSError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Average error on the test set |
|
|
//| Its meaning for regression task is obvious. As for classification|
|
|
//| problems, average error means error when estimating posterior |
|
|
//| probabilities. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - KNN model |
|
|
//| XY - test set |
|
|
//| NPoints - test set size |
|
|
//| RESULT: |
|
|
//| average error |
|
|
//| NOTE: if you need several different kinds of error metrics, it |
|
|
//| is better to use KNNAllErrors() which computes all error |
|
|
//| metric with just one pass over dataset. |
|
|
//+------------------------------------------------------------------+
|
|
double CKNN::KNNAvgError(CKNNModel &model,CMatrixDouble &xy,int npoints)
|
|
{
|
|
CKNNReport rep;
|
|
//--- function call
|
|
KNNAllErrors(model,xy,npoints,rep);
|
|
//--- return result
|
|
return(rep.m_AvgError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Average relative error on the test set |
|
|
//| Its meaning for regression task is obvious. As for classification|
|
|
//| problems, average relative error means error when estimating |
|
|
//| posterior probabilities. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - KNN model |
|
|
//| XY - test set |
|
|
//| NPoints - test set size |
|
|
//| RESULT: |
|
|
//| average relative error |
|
|
//| NOTE: if you need several different kinds of error metrics, it |
|
|
//| is better to use KNNAllErrors() which computes all error |
|
|
//| metric with just one pass over dataset. |
|
|
//+------------------------------------------------------------------+
|
|
double CKNN::KNNAvgRelError(CKNNModel &model,CMatrixDouble &xy,int npoints)
|
|
{
|
|
CKNNReport rep;
|
|
//--- function call
|
|
KNNAllErrors(model,xy,npoints,rep);
|
|
//--- return result
|
|
return(rep.m_AvgRelError);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Calculates all kinds of errors for the model in one call. |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - KNN model |
|
|
//| XY - test set: |
|
|
//| * one row per point |
|
|
//| * first NVars columns store independent variables |
|
|
//| * depending on problem type: |
|
|
//| * next column stores class number |
|
|
//| in [0, NClasses) - for classification |
|
|
//| problems |
|
|
//| * next NOut columns store dependent |
|
|
//| variables - for regression problems |
|
|
//| NPoints - test set size, NPoints >= 0 |
|
|
//| OUTPUT PARAMETERS: |
|
|
//| Rep - following fields are loaded with errors for both |
|
|
//| regression and classification models: |
|
|
//| * rep.RMSError - RMS error for the output |
|
|
//| * rep.AvgError - average error |
|
|
//| * rep.AvgRelError - average relative error |
|
|
//| following fields are set only for classification |
|
|
//| models, zero for regression ones: |
|
|
//| * relclserror - relative classification error, |
|
|
//| in [0, 1] |
|
|
//| * avgce - average cross-entropy in bits per |
|
|
//| dataset entry |
|
|
//| NOTE: the cross-entropy metric is too unstable when used to |
|
|
//| evaluate KNN models (such models can report exactly zero |
|
|
//| probabilities), so we do not recommend using it. |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNAllErrors(CKNNModel &model,CMatrixDouble &xy,
|
|
int npoints,CKNNReport &rep)
|
|
{
|
|
//--- Clean up report
|
|
ClearReport(rep);
|
|
//--- Quick exit if needed
|
|
if(model.m_isdummy || npoints==0)
|
|
return;
|
|
//--- create variables
|
|
CKNNBuffer buf;
|
|
CRowDouble desiredy;
|
|
CRowDouble errbuf;
|
|
int ny=0;
|
|
int j=0;
|
|
int nvars=model.m_nvars;
|
|
int nout=model.m_nout;
|
|
bool iscls=model.m_iscls;
|
|
//---
|
|
if(iscls)
|
|
ny=1;
|
|
else
|
|
ny=nout;
|
|
//--- Check input
|
|
if(!CAp::Assert(npoints>=0,__FUNCTION__+": npoints<0"))
|
|
return;
|
|
if(!CAp::Assert(xy.Rows()>=npoints,__FUNCTION__+": rows(xy)<npoints"))
|
|
return;
|
|
if(!CAp::Assert(xy.Cols()>=nvars+ny,__FUNCTION__+": cols(xy)<nvars+nout"))
|
|
return;
|
|
if(!CAp::Assert(CApServ::IsFiniteMatrix(xy,npoints,nvars+ny),__FUNCTION__+": xy parameter contains INFs or NANs"))
|
|
return;
|
|
//--- Process using local buffer
|
|
KNNCreateBuffer(model,buf);
|
|
if(iscls)
|
|
CBdSS::DSErrAllocate(nout,errbuf);
|
|
else
|
|
CBdSS::DSErrAllocate(-nout,errbuf);
|
|
desiredy.Resize(ny);
|
|
ulong parts[]={nvars,nout};
|
|
matrix<double> splitted[];
|
|
//--- check
|
|
xy.Split(parts,1,splitted);
|
|
for(int i=0; i<npoints; i++)
|
|
{
|
|
buf.m_x=splitted[0].Row(i);
|
|
desiredy=splitted[1].Row(i);
|
|
if(iscls)
|
|
{
|
|
j=(int)MathRound(desiredy[0]);
|
|
//--- check
|
|
if(!CAp::Assert(j>=0 && j<nout,__FUNCTION__+": one of the class labels is not in [0,NClasses)"))
|
|
return;
|
|
}
|
|
ProcessInternal(model,buf);
|
|
CBdSS::DSErrAccumulate(errbuf,buf.m_y,desiredy);
|
|
}
|
|
CBdSS::DSErrFinish(errbuf);
|
|
//--- Extract results
|
|
if(iscls)
|
|
{
|
|
rep.m_RelCLSError=errbuf[0];
|
|
rep.m_AvgCE=errbuf[1];
|
|
}
|
|
rep.m_RMSError=errbuf[2];
|
|
rep.m_AvgError=errbuf[3];
|
|
rep.m_AvgRelError=errbuf[4];
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: allocation |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNAlloc(CSerializer &s,CKNNModel &model)
|
|
{
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
s.Alloc_Entry();
|
|
if(!model.m_isdummy)
|
|
CNearestNeighbor::KDTreeAlloc(s,model.m_tree);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: serialization |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNSerialize(CSerializer &s,CKNNModel &model)
|
|
{
|
|
s.Serialize_Int(CSCodes::GetKNNSerializationCode());
|
|
s.Serialize_Int(m_knnfirstversion);
|
|
s.Serialize_Int(model.m_nvars);
|
|
s.Serialize_Int(model.m_nout);
|
|
s.Serialize_Int(model.m_k);
|
|
s.Serialize_Double(model.m_eps);
|
|
s.Serialize_Bool(model.m_iscls);
|
|
s.Serialize_Bool(model.m_isdummy);
|
|
if(!model.m_isdummy)
|
|
CNearestNeighbor::KDTreeSerialize(s,model.m_tree);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Serializer: unserialization |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::KNNUnserialize(CSerializer &s,CKNNModel &model)
|
|
{
|
|
//--- check correctness of header
|
|
int i0=s.Unserialize_Int();
|
|
if(!CAp::Assert(i0==CSCodes::GetKNNSerializationCode(),__FUNCTION__+": stream header corrupted"))
|
|
return;
|
|
int i1=s.Unserialize_Int();
|
|
if(!CAp::Assert(i1==m_knnfirstversion,__FUNCTION__+": stream header corrupted"))
|
|
return;
|
|
//--- Unserialize data
|
|
model.m_nvars=s.Unserialize_Int();
|
|
model.m_nout=s.Unserialize_Int();
|
|
model.m_k=s.Unserialize_Int();
|
|
model.m_eps=s.Unserialize_Double();
|
|
model.m_iscls=s.Unserialize_Bool();
|
|
model.m_isdummy=s.Unserialize_Bool();
|
|
if(!model.m_isdummy)
|
|
CNearestNeighbor::KDTreeUnserialize(s,model.m_tree);
|
|
//--- Prepare local buffer
|
|
KNNCreateBuffer(model,model.m_buffer);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Sets report fields to their default values |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::ClearReport(CKNNReport &rep)
|
|
{
|
|
rep.m_RelCLSError=0;
|
|
rep.m_AvgCE=0;
|
|
rep.m_RMSError=0;
|
|
rep.m_AvgError=0;
|
|
rep.m_AvgRelError=0;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| This function processes buf.X and stores result to buf.Y |
|
|
//| INPUT PARAMETERS: |
|
|
//| Model - KNN model |
|
|
//| Buf - processing buffer. |
|
|
//| IMPORTANT: buffer object should be used only with model which was|
|
|
//| used to initialize buffer. Any attempt to use buffer |
|
|
//| with different object is dangerous - you may get |
|
|
//| integrity check failure (exception) because sizes of |
|
|
//| internal arrays do not fit to dimensions of the model |
|
|
//| structure. |
|
|
//+------------------------------------------------------------------+
|
|
void CKNN::ProcessInternal(CKNNModel &model,CKNNBuffer &buf)
|
|
{
|
|
//--- Quick exit if needed
|
|
buf.m_y.Fill(0);
|
|
if(model.m_isdummy)
|
|
return;
|
|
//--- create variables
|
|
int nncnt=0;
|
|
double v=0;
|
|
int nvars=model.m_nvars;
|
|
int nout=model.m_nout;
|
|
bool iscls=model.m_iscls;
|
|
//--- Perform request, average results
|
|
nncnt=CNearestNeighbor::KDTreeTsQueryAKNN(model.m_tree,buf.m_treebuf,buf.m_x,model.m_k,true,model.m_eps);
|
|
v=1/CApServ::Coalesce(nncnt,1);
|
|
if(iscls)
|
|
{
|
|
CNearestNeighbor::KDTreeTsQueryResultsTags(model.m_tree,buf.m_treebuf,buf.m_tags);
|
|
for(int i=0; i<nncnt; i++)
|
|
{
|
|
int j=buf.m_tags[i];
|
|
buf.m_y.Add(j,v);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CNearestNeighbor::KDTreeTsQueryResultsXY(model.m_tree,buf.m_treebuf,buf.m_xy);
|
|
for(int i=0; i<nncnt; i++)
|
|
for(int j=0; j<nout; j++)
|
|
buf.m_y.Add(j,v*buf.m_xy.Get(i,nvars+j));
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|