// Copyright (C) 2001 Jean-Marc Valin


#include "TrainingAlgo.h"
#include "FFNet.h"
#include <vector>
#include "Vector.h"

using namespace std;

namespace FD {

//@require FFNet
//@implements FFNetTrain

void TrainingDeltaBarDelta::train(FFNet *net, vector<float *> tin, vector<float *> tout, 
				  int iter, float learnRate, float increase, float decrease, 
				  int nbSets, bool rprop)
{
   int i,j;
   double SSE;
   int k=1;

   int nbWeights = 0;
   cerr << "training\n";
   const float *weights = net->getWeights();
   const Vector<RCPtr<FFLayer> > &layers=net->getLayers();
   const Vector<int> &topo = net->getTopo();


   for (i=0;i<layers.size();i++)
   {
      nbWeights += layers[i]->getNbWeights();
   }

   cerr << "found " << nbWeights << " weights\n";

   Array<float> alpha(nbWeights);
   Array<float> wk(nbWeights);
   Array<float> nextW(nbWeights);
   Array<double> dEk(nbWeights);
   Array<double> nextdE(nbWeights);
   Array<double> tmpdE(nbWeights);
   double nextE;
   double tmpE;

   vec_copy(weights, &wk[0], nbWeights);

   for (i=0;i<nbWeights;i++)
      alpha[i] = learnRate;

   for (i=0;i<nbWeights;i++)
      tmpdE[i]=0;
   tmpE=0;
   net->calcGradient(tin, tout, wk, dEk, SSE);
   while (iter--)
   {

      //float norm = dEk.norm();
      float norm_1 = 1;
      if (!rprop)
	 norm_1 = 1.0/tin.size();// / norm;
      
      if (rprop)
      {
	 for (i=0;i<nbWeights;i++)
	 {
	    if (dEk[i]>0)
	       dEk[i] = 1;
	    else if (dEk[i]<0)
	       dEk[i] = -1;
	    else 
	       dEk[i] = 0;
	 }
      }
      
      if (nbSets ==1)
      {
	 for (i=0;i<nbWeights;i++)
	    nextW[i] = wk[i] - alpha[i] * norm_1 * dEk[i];
	 net->calcGradient(tin, tout, nextW, nextdE, nextE);
      } else {
	 for (i=0;i<nbWeights;i++)
	    nextW[i] = wk[i];
	 
	 nextE=0;
	 for (i=0;i<nbWeights;i++)
	    nextdE[i] = 0;
	 
	 vector<int> remaining(tin.size());
	 for (i=0;i<tin.size();i++)
	    remaining[i]=i;
	 vector<float *> batchIn;
	 vector<float *> batchOut;
	 for (i=0;i<nbSets;i++)
	 {
	    batchIn.resize(0);
	    batchOut.resize(0);
	    for (j=0;j<nbWeights;j++)
	       nextW[j] -= alpha[j] * norm_1 * tmpdE[j];
	    
	    int putBack=0;
	    for (j=0;j<remaining.size();j++)
	    {
	       int id = remaining[j];
	       if (rand()%(nbSets-i) == 0)
	       {
		  batchIn.push_back(tin[id]);
		  batchOut.push_back(tout[id]);
	       } else {
		  remaining[putBack++] = id;
	       }
	    }
	    remaining.resize(putBack);
	    net->calcGradient(batchIn, batchOut, nextW, tmpdE, tmpE);
	    nextE+=tmpE;
	    for (j=0;j<nbWeights;j++)
	       nextdE[j] += tmpdE[j];
	 }
      }
      
      if (nextE > SSE)
      {
	 alpha *= decrease;
	 //So that the "bad" iteration doesn't count
	 iter++;
	 cerr << "backing off\n";
	 /*for (i=0;i<nbWeights;i++)
	   tmpdE[i]=0;*/
	 continue;
      }

      for (i=0;i<nbWeights;i++)
      {
	 if (nextdE[i]*dEk[i] >= 0)
	    alpha[i] *= increase;
	 else
	    alpha[i] *= decrease;
	 if (alpha[i] < 1e-58)
	    alpha[i] = 1e-58;
      }

      //if (SSE/tin.size()/topo[topo.size()-1]<.08) break;
      cout << (SSE/tin.size()/topo[topo.size()-1]) << "\t" << tin.size() << endl;
      SSE=nextE;
      dEk = nextdE;
      wk = nextW;

   }

   net->setWeights(&wk[0]);
   //vec_copy(&wk[0], net->weights, nbWeights);
}





void TrainingSCG::train(FFNet *net, vector<float *> tin, vector<float *> tout, int iter, float sigma, float lambda)
{


   int i,j;
   double SSE;
   int k=1;
   float lambda_init = lambda;
   float lambdaBar = 0;
   float sigmak;
   bool success = true;

   int nbWeights = 0;

   const float *weights = net->getWeights();
   const Vector<RCPtr<FFLayer> > &layers=net->getLayers();
   const Vector<int> &topo = net->getTopo();

   for (i=0;i<layers.size();i++)
   {
      nbWeights += layers[i]->getNbWeights();
   }


   cerr << "WARNING: This is still experimental" << endl;
   cerr << "found " << nbWeights << " weights\n";


   Array<float> pk(nbWeights);
   Array<float> rk(nbWeights);
   Array<float> sk(nbWeights);
   Array<float> wk(nbWeights);
   Array<float> dEk(nbWeights);
   Array<float> dEp(nbWeights);
   Array<float> nextdE(nbWeights);

   Array<double> tmp(nbWeights);

   double nextE;
   float deltak;

   vec_copy(weights, &wk[0], nbWeights);


   net->calcGradient(tin, tout, wk, tmp, SSE);
   for (int i=0;i<nbWeights;i++)
      dEk[i] = tmp[i];



   pk=-dEk;
   rk=-dEk;
   while (k < iter)
   {

      float norm2 = pk.norm2();
      float norm = sqrt(norm2);

      //2. If success
      if (success)
      {
	 sigmak = sigma / norm;
	 double dummy = 0;

	 net->calcGradient(tin, tout, wk+pk*sigmak, tmp, dummy);
	 for (int i=0;i<nbWeights;i++)
	    dEp[i] = tmp[i];


	 sk = (dEp - dEk)*(1/sigmak);
	 deltak = pk*sk;
      }
      
      //3. Scale
      sk += pk*(lambda - lambdaBar);
      deltak += (lambda - lambdaBar) * norm2;
      
      //4. Hessian
      if (deltak <= 0)
      {
	 cerr << "Hessian not positive definite\n";
	 sk += pk*(lambda - 2*deltak/norm2);
	 lambdaBar = 2*(lambda - deltak/norm2);
	 deltak = -deltak + lambda*norm2;
	 lambda = lambdaBar;
      }

      //5. Step size
      float uk = pk * rk;
      float ak = uk/deltak;

      //ak = .000000001;
      //pk = -rk;

      //6. Comparison
      net->calcGradient(tin, tout, wk+pk*ak, tmp, nextE);
      for (int i=0;i<nbWeights;i++)
	 nextdE[i] = tmp[i];

      float DK = 2*deltak*(SSE -  nextE) / (uk*uk);

      //cerr << SSE << " " << nextE << " " << ak << " " << uk << " " << norm << endl;

      //7. Can we reduce the error
      if (DK >= 0)
      {
	 wk += pk*ak;
	 Array<float> oldR = rk;
	 SSE=nextE;
	 rk = -nextdE;
	 lambdaBar = 0;
	 success = true;
	 cout << SSE/tin.size()/topo[topo.size()-1] << "\t" << DK << "\t" << lambda << "\t" << norm << "\t" << ak << "\t" << endl;
	 if (k%nbWeights == 0)
	 {
	    pk = rk;
	    k++;
	    lambda = lambda_init;
	    lambdaBar = 0;
	    cerr << "restarting\n";
	    continue;
	 } else {
	    float bk = (rk.norm2() - rk*oldR)/uk;
	    pk = rk + pk * bk;
	 }
	 if (DK >= .75 && lambda > 1e-100)
	    lambda *= .5;
	 k++;
      } else {
	 lambdaBar = lambda;
	 success = false;
      }

      //8. increase scale
      if (DK < .25 && lambda < 1e200)
	 lambda *= 4;
      
      //9. Have we found the minimum
      if (rk.norm() == 0)
	 break;
      //k++;
   }
   net->setWeights(&wk[0]);
}


void TrainingQProp::train(FFNet *net, vector<float *> tin, vector<float *> tout, int iter, float learnRate)
{

   int i,j;
   double SSE;
   int k=1;

   float increase=1.04;
   float decrease = .6;

   int nbWeights = 0;

   const float *weights = net->getWeights();
   const Vector<RCPtr<FFLayer> > &layers=net->getLayers();
   const Vector<int> &topo = net->getTopo();

   for (i=0;i<layers.size();i++)
   {
      nbWeights += layers[i]->getNbWeights();
   }
   cerr << "WARNING: This implementation of Quickprop doesn't work yet!" << endl;
   cerr << "found " << nbWeights << " weights\n";

   //Array<float> alpha(nbWeights);
   Array<float> wk(nbWeights);
   Array<float> nextW(nbWeights);

   Array<double> dW(nbWeights,0);
   // Array<double> dWq(nbWeights,0);
   Array<double> prevdW(nbWeights,0);
   Array<double> dE(nbWeights,0);
   Array<double> nextdE(nbWeights,0);
   Array<double> prevdE(nbWeights,0);

   //Array<double> nextdE(nbWeights);
   double nextE;

   vec_copy(weights, &wk[0], nbWeights);

   //for (i=0;i<nbWeights;i++)
   //   alpha[i] = learnRate;

   net->calcGradient(tin, tout, wk, dE, SSE);
   while (iter--)
   {

      for (i=0;i<nbWeights;i++)
      {
	 double norm = dE[i]-prevdE[i];
	 if (fabs(norm) > 1e-8)
	    dW[i] = -(dE[i]*prevdW[i])/norm;
	 else
	    dW[i] = 0;

	 if ((dE[i]*prevdE[i]) <= 0)
	    dW[i]-= learnRate*dE[i];
      }
      

      for (i=0;i<nbWeights;i++)
	 nextW[i] = wk[i] + dW[i];

      net->calcGradient(tin, tout, nextW, nextdE, nextE);

      while(nextE > SSE)
      {
	 float alpha = learnRate;
	 for (i=0;i<nbWeights;i++)
	    nextW[i] = wk[i] - alpha*dE[i];
	 net->calcGradient(tin, tout, nextW, nextdE, nextE);
	 alpha *= .5;
      }


      //if (SSE/tin.size()/topo[topo.size()-1]<.08) break;
      cout << (SSE/tin.size()/topo[topo.size()-1]) << "\t" << tin.size() << endl;
      prevdW=dW;
      prevdE=dE;
      SSE=nextE;
      dE = nextdE;
      wk = nextW;
      //nextdE = dE;
   }

   net->setWeights(&wk[0]);
   //vec_copy(&wk[0], weights, nbWeights);


}



void TrainingWeightDeltaBarDelta::train(FFNet *net, vector<float *> tin, vector<float *> tout, vector<float *> learnWeights, int iter, float learnRate, 
			 float increase, float decrease)
{
   int i,j;
   double SSE;
   int k=1;

   int nbWeights = 0;

   const float *weights = net->getWeights();
   const Vector<RCPtr<FFLayer> > &layers=net->getLayers();
   const Vector<int> &topo = net->getTopo();


   for (i=0;i<layers.size();i++)
   {
      nbWeights += layers[i]->getNbWeights();
   }

   cerr << "found " << nbWeights << " weights\n";

   Array<float> alpha(nbWeights);
   Array<float> wk(nbWeights);
   Array<float> nextW(nbWeights);
   Array<double> dEk(nbWeights);
   Array<double> nextdE(nbWeights);
   double nextE;

   vec_copy(weights, &wk[0], nbWeights);

   for (i=0;i<nbWeights;i++)
      alpha[i] = learnRate;

   net->weightedCalcGradient(tin, tout, learnWeights, wk, dEk, SSE);
   while (iter--)
   {

      float norm = dEk.norm();
      float norm_1 = 1;// / norm;
      
      for (i=0;i<nbWeights;i++)
	 nextW[i] = wk[i] - alpha[i] * norm_1 * dEk[i];
      net->weightedCalcGradient(tin, tout, learnWeights, nextW, nextdE, nextE);
      
      if (nextE > SSE)
      {
	 alpha *= decrease;
	 //So that the "bad" iteration doesn't count
	 iter++;
	 cerr << "backing off\n";
	 continue;
      }

      for (i=0;i<nbWeights;i++)
      {
	 if (nextdE[i]*dEk[i] >= 0)
	    alpha[i] *= increase;
	 else
	    alpha[i] *= decrease;
	 if (alpha[i] < 1e-58)
	    alpha[i] = 1e-58;
      }

      //if (SSE/tin.size()/topo[topo.size()-1]<.08) break;
      cout << (SSE/tin.size()/topo[topo.size()-1]) << "\t" << tin.size() << endl;
      SSE=nextE;
      dEk = nextdE;
      wk = nextW;

   }

   net->setWeights(&wk[0]);
   //vec_copy(&wk[0], net->weights, nbWeights);
}
}//namespace FD


syntax highlighted by Code2HTML, v. 0.9.1