unit ulearningcfcerebros;

{$mode delphi}

interface

uses
  Classes, SysUtils,
  Math,
  xmatdefs, utrayectorias_xcdpcf, uclasificador,
  uadministradordeestadoscerebro, matreal,
  utipos_xcf,
  ufxgx, uresfxgx,
  uproblema,
  uparsimoniat,
  useriestemporales,
  uneuronas;


type


  { TCF_fCerebrosConParsimonia }

  TCF_fCerebrosConParsimonia = class(Tfx)
    cerebros: TDAOfCerebros;
    Data: TMatOfRecXCF;
    parsimonia: TParsimonia;

    constructor Create(cerebros: TDAOfCerebros; Data: TMatOfRecXCF;
      parsimonia: TParsimonia);

    procedure SetParametrosCerebros(const X: TVectR);
    function f(const X: TVectR): NReal; override;
    procedure acum_g(var grad: TVectR; const X: TVectR); override;

    function EvalYDarPaso(var grad_ant: TVectR; var X: TVectR; NMuestras: integer;
      paso, beta_momento, gamma_momento: NReal): NReal;

  end;


  { TCF_fCerebros }

  TCF_fCerebros = class(Tfx)
    cerebros: TDAOfCerebros;
    Data: TMatOfRecXCF;

    constructor Create(cerebros: TDAOfCerebros; Data: TMatOfRecXCF);

    procedure SetParametrosCerebros(const X: TVectR);
    function f(const X: TVectR): NReal; override;
    procedure acum_g(var grad: TVectR; const X: TVectR); override;

  end;

  { TCF_rParsimonia }

  TCF_rParsimonia = class(Tfx)
    cerebro: TCerebro;
    parsimonia: TParsimonia;
    kPaso:Integer;

    constructor Create(cerebro: TCerebro; parsimonia: TParsimonia; kPaso: Integer);

    //procedure SetParametrosCerebros(const X: TVectR);
    function f(const X: TVectR): NReal; override;
    procedure acum_g(var grad: TVectR; const X: TVectR); override;
  end;

function AjusteCFCerebro(CF: TAdminEstadosCerebro; trayectorias: TTrayectorias;
  parsimonia: TParsimonia; learningRate: NReal ): NReal;


function EntrenarCerebrosConParsimonia_aData(var aCerebros: TDAOfCerebros; // Cerebro
  aData: TMatOfRecXCF;  // Datos de entrenamiento

  parsimonia: TParsimonia; //Parsimonia

  MaxNIter: integer; // Número máximo de iteracciones
  out flgConvergio: boolean;
  // retorna TRUE si convergió, FALSE si salio por número máximo de iters
  out cntIters: integer; kCapa, kNeurona:
  integer // Si kCapa < 0 se optimizan todas, si kCapa >= 0 kNeurona >= 0 se optimiza
  // solo esa neurona
  ): NReal;

function EntrenarCerebrosConParsimonia_aData_MaxInBox(var aCerebros: TDAOfCerebros; // Cerebro
  aData: TMatOfRecXCF;  // Datos de entrenamiento

  parsimonia: TParsimonia; //Parsimonia

  MaxNIter: integer; // Número máximo de iteracciones
  out flgConvergio: boolean;
  // retorna TRUE si convergió, FALSE si salio por número máximo de iters
  out cntIters: integer; kCapa, kNeurona:
  integer; // Si kCapa < 0 se optimizan todas, si kCapa >= 0 kNeurona >= 0 se optimiza
  // solo esa neurona
  problema_opt: TProblema_m01): NReal;

function EntrenarCerebro_EBP_ConParsimonia(var aCerebros: TDAOfCerebros; // Cerebro
  aData: TMatOfRecXCF;  // Datos de entrenamiento
  parsimonia: TParsimonia; paso, momento: NReal; var grad_ant: TVectR;
  var X:TVectR; CFX_fEntrenador: TCF_fCerebrosConParsimonia): NReal;



// convierte aData a una TSeriesDeDatos y lo guarda en un .sas
// guarda cerebro en formato xlt
// estos dos archivos pueden leerse en el debuger de entrenador
procedure DumpForDebug( prefijo_archi: string; cerebro: TCerebro; aData: TDAOfRecXCF );

implementation

procedure DumpForDebug( prefijo_archi: string; cerebro: TCerebro; aData: TDAOfRecXCF );
var
  archi: string;
  series: TSeriesDeDatos;
begin
  archi:= prefijo_archi+'_cerebro.xlt';
  cerebro.WriteToArchi( archi );
  series:= DAofRecXCF_To_SeriesDeDatos( aData );
  archi:= prefijo_archi+'_data.sas';
  series.WriteToArchi( archi );
  series.Free;
end;

procedure EntrenarMiniBatch( cerebro: TCerebro; aData: TDAOfRecXCF );
var
  parametros: TVectR;
  momento, paso, paso_fin: NREal;
  cnt_exitos, cnt_fracasos: integer;
  e2_ant: NReal;
  k, j: integer;
  alfa_regularizacion: NReal;
  e2: NReal;

begin
  momento:= 0.7;
  paso:= 0.01;
  paso_fin:= paso / 1.0E4;
  alfa_regularizacion:= 1.0E-12;

  parametros := TVectR.Create_Init(Cerebro.DimParametros);

  Cerebro.GetParametros(parametros);

  e2_ant := 1e30;
  cnt_exitos := 0;
  cnt_fracasos := 0;

  for k := 1 to 4000 do
  begin
      e2 := EntrenarCerebro_EBP_minibatch(Cerebro, aData, paso,
        momento,
        alfa_regularizacion,
         -1, 31 );

    if e2 < -1 then
      e2 := 2 * e2_ant;

    if e2 < e2_ant then
    begin
      Cerebro.GetParametros(parametros);
      cnt_fracasos := 0;
      Inc(cnt_exitos);
      if cnt_exitos > 3 then
      begin
        cnt_exitos := 0;
        paso := paso * GoldenRatio;
      end;
    end
    else
    begin
      //      Cerebro.SetParametros(parametros);
      cnt_exitos := 0;
      Inc(cnt_fracasos);
      if cnt_fracasos > 0 then
      begin
        cnt_fracasos := 0;
        paso := paso * invGoldenRatio;
      end;
    end;
    e2_ant := e2;

    Writeln(k, #9, e2, #9, paso);

    if abs( paso ) < paso_fin then
      break;
  end;
  Cerebro.SetParametros(parametros);
end;




procedure Entrenar(aCerebro_x: TCerebro; trayectorias: TTrayectorias;
  kPaso: integer );
var
  aTrayectoria: TTrayectoria;
  aData: TDAOfRecXCF;
  kCronica: integer;
  ix, ioffset: integer;
  xcf: TRecXCF;
  flg_Convergio: boolean;
  cntIters: integer;

begin
  aData:= Trayectorias.GetDAOfRecXCFOfkPaso( kPaso );

  (*  // no escalo acá porque se supone que se escalaron al Crear el Cerebro
      // teniendo en cuenta la descripción de las variables de estado.
  aCerebro_x.EscalarEntradas(aData);
  aCerebro_x.EscalarCapaSalida(aData);
  *)

  // EntrenarCerebro(aCerebro_x, aData, 10000, flg_Convergio, cntIters, -1, -1);



    DumpForDebug( 'emb'+IntToStr( kPaso ), aCerebro_x, aData );

    EntrenarMiniBatch(
      aCerebro_x, // Cerebro
      aData );

  aCerebro_x.WriteToArchi('cerebro_DVLP_' + IntToStr(kPaso) + '.xlt');
  CalcularYGuardar('rn_' + IntToStr(kPaso) + '.xlt', aCerebro_x, aData);
  free_data_xcf(aData);
end;

function EntrenarCerebrosConParsimonia(CF: TAdminEstadosCerebro;
  trayectorias: TTrayectorias; parsimonia: TParsimonia; MaxNIter: integer;
  // Número máximo de iteracciones
  out flgConvergio: boolean;
  // retorna TRUE si convergió, FALSE si salio por número máximo de iters
  out cntIters: integer; kCapa, kNeurona: integer): NReal;

var
  aTrayectoria: TTrayectoria;
  aData: TMatOfRecXCF;
  kCronica: integer;
  ix, ioffset, nparametrosX: integer;
  xcf: TRecXCF;
  flg_Convergio: boolean;
  kPaso: integer;
  resValCosto: NReal;
  problema_opt: TProblema_m01;

begin

  SetLength(aData, High(CF.Cerebros));

  for kPaso:=0 to High(aData) do
  begin
    aData [kPaso]:= utipos_xcf.Crear_data_xcf_0(trayectorias.DimX, trayectorias.nTrayectorias);
    for kCronica := 0 to trayectorias.nTrayectorias - 1 do
    begin
      xcf := aData[kPaso][kCronica];
      aTrayectoria := trayectorias.Trayectorias[kCronica];
      for ix := 0 to aTrayectoria.nr - 1 do
        xcf.X.pon_e(ix + 1, aTrayectoria.puntos[kPaso].X.Xr.e(ix + 1));
      ioffset := aTrayectoria.nr;
      for ix := 0 to aTrayectoria.nd - 1 do
        xcf.X.pon_e(ioffset + ix + 1, aTrayectoria.puntos[kPaso].X.Xd.e(ix + 1));
      xcf.Y.pon_e(1, aTrayectoria.puntos[kPaso].CF);
    end;
  end;

  nparametrosX := CF.Cerebros[1].DimParametros * ( high(CF.Cerebros) + parsimonia.dimParsimonia );
  problema_opt := TProblema_m01.Create_init(high(CF.Cerebros) + 1,
                      nparametrosX + 1, 0, nil, nil,false);

  resValCosto:= EntrenarCerebrosConParsimonia_aData_MaxInBox(
  CF.Cerebros, aData, parsimonia,MaxNIter,flgConvergio,cntIters,kCapa,kNeurona,problema_opt);

  for kPaso:=0 to High(aData) do
    free_data_xcf(aData[kPaso]);

  SetLength(aData,0);

  Result := resValCosto;

end;



function AjusteCFCerebro(CF: TAdminEstadosCerebro; trayectorias: TTrayectorias;
  parsimonia: TParsimonia; learningRate: NReal ): NReal;
var
  kPaso, nPuntosT_Actualizables, cntIters: integer;
  aCerebro: TCerebro;
  eMaxAbs, aMaxAbs: NReal;
  parametros, parametros_x: TVectR;
  sal: textfile;
  flg_Convergio: boolean;
  parametros_CF: TDAOfVectR;

begin
  nPuntosT_Actualizables := CF.nPuntosT - 1;
  if nPuntosT_actualizables > length(trayectorias.Trayectorias[0].puntos) then
    nPuntosT_actualizables := length(trayectorias.Trayectorias[0].puntos);

  aCerebro := CF.Cerebros[1];

  parametros := TVectR.Create_init(aCerebro.DimParametros);
  parametros_x := TVectR.Create_init(aCerebro.DimParametros);
  eMaxAbs := 0;

  assignfile(sal, 'parameros_cerebro_porpasos.xlt');
  rewrite(sal);

  if Parsimonia = nil then
  begin
    for kPaso := 1 to nPuntosT_Actualizables do
    begin
      aCerebro := CF.Cerebros[kPaso];
      aCerebro.GetParametros(parametros);

      Entrenar(aCerebro, trayectorias, kPaso - 1);

      aCerebro.GetParametros(parametros_x);
      aMaxAbs := Parametros.distancia(Parametros_x);
      if aMaxAbs > eMaxAbs then
        eMaxAbs := aMaxAbs;

      Parametros.PorReal(1 - learningRate);
      Parametros.sumRPV(learningRate, Parametros_X);
      aCerebro.SetParametros(Parametros);

      writeln(sal, kPaso, #9, Parametros.serialize_data(#9));
      //writeln( kPaso );
    end;
  end
  else
  begin
    setlength(parametros_CF, nPuntosT_Actualizables + 1);
    for kPaso := 1 to nPuntosT_Actualizables do
    begin
      aCerebro := CF.Cerebros[kPaso];
      parametros_CF[kPaso] := TVectR.Create_Init(aCerebro.DimParametros);
      aCerebro.GetParametros(parametros_CF[kPaso]);
    end;

    EntrenarCerebrosConParsimonia(CF, trayectorias, parsimonia, 100,
      flg_COnvergio, cntIters, -1, -1);

    for kPaso := 1 to nPuntosT_Actualizables do
    begin
      aCerebro := CF.Cerebros[kPaso];
      aCerebro.GetParametros(parametros_x);
      aMaxAbs := parametros_CF[kPaso].distancia(Parametros_x);
      if aMaxAbs > eMaxAbs then
        eMaxAbs := aMaxAbs;


      parametros_CF[kPaso].PorReal(1 - learningRate);
      parametros_CF[kPaso].sumRPV(learningRate, Parametros_X);
      aCerebro.SetParametros(parametros_CF[kPaso]);
      writeln(sal, kPaso, #9, parametros_CF[kPaso].serialize_data(#9));
      writeln(kPaso);

      parametros_CF[kPaso].Free;
    end;
    setlength(parametros_CF, 0);
  end;


  closefile(sal);
  Result := eMaxAbs;
end;



{ TCF_rParsimonia }

constructor TCF_rParsimonia.Create(cerebro: TCerebro; parsimonia: TParsimonia; kPaso:Integer);
begin
  self.cerebro:=cerebro;
  self.parsimonia:= parsimonia;
  self.kPaso:=kPaso;
end;

function TCF_rParsimonia.f(const X: TVectR): NReal;
var
  parametrosCerebro, parametrosCerebroParsimonia,X_parsimonia: TVectR;
  e:NReal;
  k, nparametrosP: Integer;
begin
  parametrosCerebro:= TVectR.Create_Init(cerebro.DimParametros);
  parametrosCerebroParsimonia:=TVectR.Create_Init(cerebro.DimParametros);

  cerebro.GetParametros(parametrosCerebro);

  nparametrosP:=parsimonia.dimParsimonia * Cerebro.DimParametros;

  X_parsimonia:=TVectR.Create_Init(nparametrosP);
  X_parsimonia.CopyFromTo(X.n-nparametrosP + 1, X.n, 1, X);

  e:=0;
  parsimonia.set_dtk(kPaso);

  parsimonia.fp(parametrosCerebroParsimonia, X_parsimonia);


  for k:=1 to cerebro.DimParametros do
    e:= e + sqr(parametrosCerebro.pv[k] - parametrosCerebroParsimonia.pv[k]);

  Result := 0.5 * e;

  parametrosCerebro.Free;
  parametrosCerebroParsimonia.Free;
end;

procedure TCF_rParsimonia.acum_g(var grad: TVectR; const X: TVectR);
var
  gpr:TMatR;
  k, kIniGradPR, kIniGradPP, nparametrosP: Integer;
  grad_aux, parametrosCerebro, parametrosCerebroParsimonia,
    X_parsimonia: TVectR;
  eg: NReal;
begin

  grad_aux:= TVectR.Create_Init(grad.n);

  kIniGradPR:= (kPaso-1)*(cerebro.DimParametros);

  kIniGradPP:= grad.n - (cerebro.DimParametros*parsimonia.dimParsimonia - 1);

  parametrosCerebro:= TVectR.Create_Init(cerebro.DimParametros);
  parametrosCerebroParsimonia:=TVectR.Create_Init(cerebro.DimParametros);

  cerebro.GetParametros(parametrosCerebro);

  nparametrosP:=parsimonia.dimParsimonia * Cerebro.DimParametros;

  X_parsimonia:=TVectR.Create_Init(nparametrosP);
  X_parsimonia.CopyFromTo(X.n-nparametrosP+1, X.n, 1, X);

  parsimonia.set_dtk(kPaso);

  parsimonia.fp(parametrosCerebroParsimonia, X_parsimonia);


  gPR:=TMatR.Create_Init(parsimonia.dimParsimonia,cerebro.DimParametros);

  //  de/dP
  parsimonia.gp(gPR);

{*
   La derivada del e2, utilizando la regla de la cadena, es

   de/dX = 2*e*[de/dRo, -de/dP]= 2*e*[1,1,.....,1;-1,-t,-t2,..,-cos,-sen,..,-1,-t....]
                 583                                 11*2=22

   X=[Ro,P]

   Donde:
   Ro=[ro_1,....,ro_nCerebros]
   ro_k=[bias1,ro1,ro2.....n_PR]

   P=[p_1,.....,p_nPR]
   p_k=[A0,A1,...An,Ca,Cb.....]


*}




  for k:=1 to cerebro.DimParametros do
  begin
    eg:= parametrosCerebro.pv[k] - parametrosCerebroParsimonia.pv[k];
    grad.pon_e(kIniGradPR + k, eg );  //  de2/dRo=e
    grad_aux.CopyFromTo(1, gpr.Columna(1).n, kIniGradPP + (k-1)*parsimonia.dimParsimonia , gpr.Columna(1), eg );

  end;


  //El auxiliar esta porque va restado solo las derivadas respecto a los parametros de la parsimonia
  grad.res(grad_aux);

  grad_aux.Free;
  gPR.Free;
end;

{ TCF_fCerebros }

constructor TCF_fCerebros.Create(cerebros: TDAOfCerebros; Data: TMatOfRecXCF);
begin
  self.cerebros := cerebros;
  self.Data := Data;
end;

procedure TCF_fCerebros.SetParametrosCerebros(const X: TVectR);
var
  aCerebro: TCerebro;
  kPaso, kini, kfin, nParametros_Cerebro: integer;
  X_cerebro: TVectR;
begin


  X_cerebro:= TVectR.Create_Init(cerebros[1].DimParametros);
  nParametros_Cerebro:= cerebros[1].DimParametros;
  kini:=1;
  kfin:=nParametros_Cerebro;

  for kPaso := 1 to high(Cerebros) do
  begin
    X_cerebro.CopyFromTo(kini,kfin,1,X);
    aCerebro := Cerebros[kPaso];
    aCerebro.SetParametros(X_cerebro);
    kini:= kini+nParametros_Cerebro;
    kfin:= kfin+nParametros_Cerebro;
    X_cerebro.Ceros;
  end;

  X_cerebro.Free;

end;


function TCF_fCerebros.f(const X: TVectR): NReal;
var
  acum_e2: NReal;
  kRec: integer;
  aRec: TRecXCF;
  aCerebro: TCerebro;
  fDivisor: NReal;
  aData: TDAOfRecXCF;
  kPaso: integer;
  derr: NReal;

begin
  SetParametrosCerebros(X);

  aCerebro := Cerebros[1];


  fDivisor := 1 / ((high(Cerebros)) * Length(Data[0]));

  acum_e2 := 0;
  derr:=0;

  for kPaso := 1 to high(Cerebros) do
  begin
    derr:=0;
    aCerebro := Cerebros[kPaso];
    aCerebro.BackPropagation_init;
    aData := Data[kPaso - 1];
    for kRec := 0 to high(aData) do
    begin
      aRec := aData[kRec];
      aCerebro.SetEntradas(aRec.X);
      aCerebro.evaluar;
      acum_e2 := acum_e2 + aCerebro.calc_err2(derr, aRec.CF);
      aCerebro.BackPropagation_ClearErr;
      aCerebro.BackPropagation_addMuestra(derr);
    end;
    // como efecto secundario del for anterior, el Cerebro queda cargado
    // con las derivadas del error cuadrático del paso kPaso respecto de
    // los Parametros Reales (del cerebro del paso).
  end;
  Result := acum_e2 * fDivisor;

end;

procedure TCF_fCerebros.acum_g(var grad: TVectR; const X: TVectR);
var
  aCerebro: TCerebro;
  grad_k: TVectR;
  kini,kPaso, nParametros_Cerebro:integer;
  fDivisor: NReal;
begin
  kini:=1;
  nParametros_Cerebro:=cerebros[1].DimParametros;
  grad_k:=TVectR.Create_Init(nParametros_Cerebro);

  fDivisor := 1 / (high(Cerebros) * Length(Data[0]));

  for kPaso := 1 to high(Cerebros) do
  begin
    grad_k.Ceros;
    aCerebro := Cerebros[kPaso];
    aCerebro.AcumGradParametros(grad_k);
    grad.CopyFromTo(1,grad_k.n,kini,grad_k); //Concateno los gradientes de los cerebros
    kini:=kini + nParametros_Cerebro;
  end;

  grad.PorReal(fDivisor);
end;



{ TCF_fCerebrosConParsimonia }

constructor TCF_fCerebrosConParsimonia.Create(cerebros: TDAOfCerebros;
  Data: TMatOfRecXCF; parsimonia: TParsimonia);
begin
  self.cerebros := cerebros;
  self.Data := Data;
  self.parsimonia := parsimonia;
end;

procedure TCF_fCerebrosConParsimonia.SetParametrosCerebros(const X: TVectR);
var
  parametros: TVectR;
  aCerebro: TCerebro;
  kPaso: integer;

begin

  aCerebro := Cerebros[1];
  parametros := TVectR.Create_Init(aCerebro.DimParametros);

  for kPaso := 1 to high(Cerebros) do
  begin
    aCerebro := Cerebros[kPaso];

    parsimonia.set_dtk(kPaso);

    parsimonia.fp(parametros, X);

    aCerebro.SetParametros(parametros);

  end;
  parametros.Free;

end;

function TCF_fCerebrosConParsimonia.f(const X: TVectR): NReal;
var
  acum_e2: NReal;
  kRec: integer;
  aRec: TRecXCF;
  aCerebro: TCerebro;
  fDivisor: NReal;
  aData: TDAOfRecXCF;
  kPaso: integer;
  derr: NReal;

begin
  SetParametrosCerebros(X);

  aCerebro := Cerebros[1];


  fDivisor := 1 / (length(Cerebros) * Length(Data[0]));

  acum_e2 := 0;
  derr:=0;

  for kPaso := 1 to high(Cerebros) do
  begin
    derr:=0;
    aCerebro := Cerebros[kPaso];
    aCerebro.BackPropagation_init;
    aData := Data[kPaso - 1];
    for kRec := 0 to high(aData) do
    begin
     // aCerebro.BackPropagation_init;
      aRec := aData[kRec];
      aCerebro.SetEntradas(aRec.X);
      aCerebro.evaluar;
      acum_e2 := acum_e2 + aCerebro.calc_err2(derr, aRec.CF);
      aCerebro.BackPropagation_ClearErr;
      aCerebro.BackPropagation_addMuestra(derr);
    end;
    // como efecto secundario del for anterior, el Cerebro queda cargado
    // con las derivadas del error cuadrático del paso kPaso respecto de
    // los Parametros Reales (del cerebro del paso).
  end;
  Result := acum_e2 * fDivisor;
end;

procedure TCF_fCerebrosConParsimonia.acum_g(var grad: TVectR; const X: TVectR);
var
  aCerebro: TCerebro;

  kPaso: integer;
  kpp: integer;
  kPar: integer;

  gpr_paso: TVectR;

  gprpp: TMatR;
  fDivisor: Extended;
  // ahora, se trata de recorrer nuevamente los pasos coleccionando
  // los gradientes del error cuadrático respecto de los Parametros de Parsimonia

begin

  aCerebro := Cerebros[1];

  grad.Ceros;

  gprpp := TMatR.Create_Init(parsimonia.dimParsimonia, aCerebro.DimParametros);

  gpr_paso := TVectR.Create_INit(aCerebro.DimParametros);


  fDivisor := 1 / (length(Cerebros) * Length(Data[0]));

  for kPaso := 1 to high(Cerebros) do
  begin
    aCerebro := Cerebros[kPaso];

    parsimonia.set_dtk(kPaso);
    parsimonia.gp(gprpp);

    gpr_paso.Ceros;
    aCerebro.AcumGradParametros(gpr_paso);

    // ahora calculamos el producto gprpp * gpr_paso y lo acumulamos
    // en grad
    for kpar := 1 to gprpp.nc do
      for kpp := 1 to parsimonia.dimParsimonia do
        grad.acum_e(kpp + (kPar - 1) * parsimonia.dimParsimonia,
          gprpp.e(kpp, kpar) * gpr_paso.e(kpar));



    {  gpr_paso.WriteXLTSimple_archi('dE_dr_'+IntToStr(kPaso)+'.txt' );
      gprpp.WriteArchiXLT('gprpp_'+IntToStr(kPaso)+'.txt' );
      grad.WriteXLTSimple_archi('dE_dp_'+IntToStr(kPaso)+'.txt' );
      parsimonia.vpot.WriteXLTSimple_archi('parsimonia'+IntToStr(kPaso)+'.txt' );  }
      //parsimonia.vcos.WriteXLTSimple_archi('parsimonia'+IntToStr(kPaso)+'.txt' );
   // readln;

  end;

  grad.PorReal(fDivisor);

  gpr_paso.Free;
  gprpp.Free;

end;

function TCF_fCerebrosConParsimonia.EvalYDarPaso(var grad_ant: TVectR;
  var X: TVectR; NMuestras: integer; paso, beta_momento, gamma_momento: NReal): NReal;
var
  grad: TVectR;
  k: integer;
  m: NReal;
begin

  grad := TVectR.Create_Init(grad_ant.n);
  grad.Ceros;

  Result := f(X);
  self.acum_g(grad, X);

  m := gamma_momento; /// NMuestras;
  for k := 1 to grad_ant.n do
    grad_ant.pon_e(k, grad_ant.e(k) * beta_momento + grad.e(k) * m);

  X.SumRPV(-paso, grad_ant);

  grad.Free;

end;


procedure chequearGrad(X:TVectR;CFX_fEntrenador:Tfx);
var
  f:TextFile;
  Err0, Err_s: NReal;
  grad, Xs: TVectR;
  k:Integer;
  delta_x: NReal;
begin

  AssignFile(f,'chequearGrad.xlt');

  Rewrite(f);

  grad:=TVectR.Create_Init(X.n);
  Xs:=TVectR.Create_Init(X.n);

  X.WriteXLTSimple(f);

  Err0:=CFX_fEntrenador.f(X);

  WriteLn(f,'f(x)',#9,err0);

  CFX_fEntrenador.acum_g(grad,X);

  grad.WriteXLTSimple(f);

  delta_X:= 0.00001;
  for k:=1 to X.n do
  begin
    Xs.Copy(X);
    Xs.acum_e(k, delta_x);

    Err_s:=CFX_fEntrenador.f(Xs);
    WriteLn(f,'fs(x)'+ IntToStr(k),#9, (err_s-err0)/delta_x);
  end;

  CloseFile(f);

  grad.free;
  Xs.Free;

end;

function EntrenarCerebrosConParsimonia_aData(var aCerebros: TDAOfCerebros;
  aData: TMatOfRecXCF; parsimonia: TParsimonia; MaxNIter: integer;
  out flgConvergio: boolean; out cntIters: integer; kCapa, kNeurona: integer): NReal;
var
  CFX_fEntrenador: TCF_fCerebrosConParsimonia;
  nParametros: integer;
  X, Xs: TVectR;
  problema_opt: TProblema_m01;
  k: integer;
  resValCosto, resValLagrangiana: NReal;
  resErr: NReal;
  kOffset: integer;

begin

  CFX_fEntrenador := TCF_fCerebrosConParsimonia.Create(aCerebros, aData, parsimonia);



  nparametros := parsimonia.dimParsimonia * aCerebros[1].DimParametros;

  X := TVectR.Create_init(nparametros);
  Xs := TVectR.Create_init(nparametros);

  //  writeln('Inicio Opt por Problema ... ', kCapa,' ', kNeurona );

  problema_opt := TProblema_m01.Create_init(1, nparametros + 1, 0, nil, nil);
  problema_opt.f := CFX_fEntrenador;
  for k := 1 to nparametros do
  begin
    problema_opt.cota_inf_set(k, -1E50);
    problema_opt.cota_sup_set(k, 1E50);
  end;

  Randomize;
  X.random();
  X.MasReal(0.001);


  //chequearGrad(X,CFX_fEntrenador);

  resErr := problema_opt.MinInBox_CPX(X, 100, 1E-25, MaxNIter, cntIters,
    resValLagrangiana, resValCOsto, flgConvergio, False);



  CFX_fEntrenador.SetParametrosCerebros(X);

  X.Free;
  Xs.Free;
  CFX_fEntrenador.Free;
  Result := resValCosto;

end;

function EntrenarCerebrosConParsimonia_aData_MaxInBox(
  var aCerebros: TDAOfCerebros; aData: TMatOfRecXCF; parsimonia: TParsimonia;
  MaxNIter: integer; out flgConvergio: boolean; out cntIters: integer; kCapa,
  kNeurona: integer; problema_opt: TProblema_m01): NReal;
var
  CFX_Entrenador_f: TCF_fCerebros;
  nParametros: integer;
  X,  Lambda: TVectR;
  k: integer;
  resErr,resValCosto, resValLagrangiana,dfrontera: NReal;
  kPaso: integer;
  aR: TResfx;

begin

  //La funcion f a minimizar es
  //La acumulacion del error cuadratico medio de todos los cerebros (como si no tuviera parsimonia)
  //y el grad es derivada del error respecto a los parametros reales de los cerebros
  CFX_Entrenador_f:= TCF_fCerebros.Create(aCerebros, aData);

  nparametros := aCerebros[1].DimParametros * ( high(aCerebros) + parsimonia.dimParsimonia );

  X := TVectR.Create_init(nparametros);

  {*
   X=[Ro,P] ----> length(X)= nCerebros*nPR + nPR*nPP = nPR*(nCerebros+nPP)

   Donde:
   Ro=[ro_1,....,ro_nCerebros] ----> length(Ro)=Cantidad de cerebros = Cantidad de pasos de tiempo (un cerebro por paso de tiempo)
   ro_k=[bias1,ro1,ro2.....n_PR]----> length(ro_k)=Cantidad de parametros del Cerebro (parametros reales)

   Y:

   P=[p_1,.....,p_nPR]----> length(P)=Cantidad de parametros del Cerebro (parametros reales)
   p_k=[A0,A1,...An,Ca,Cb.....]----> length(p_k)=Cantidad de parametros de la Parsimonia

  *}

  problema_opt.f := CFX_Entrenador_f;

  Lambda := TVectR.Create_InitVal(high(aCerebros),1); // crea el vector de los multiplicadores de Lagrange, nPasos resticción
  randomize;


  for kPaso:=1 to high(aCerebros) do
  begin
    aR := problema_opt.restricciones[kPaso-1];
    aR.tipo := TR_Igualdad;
    //Hay 1 restriccion por paso de tiempo
    //La f es el error entre los parametros reales del cerebro y los parametros reales evaluados con la parsimonia
    //En acum_g esta su derivada respecto de los parametros de la parsimonia y de los parametros reales
    aR.fx := TCF_rParsimonia.Create(aCerebros[kPaso], parsimonia,kpaso);
    aR.f_min := 0;
    aR.f_max := 0;
    aR.lambda := 0;
  end;
  for k := 1 to nparametros do
  begin
    problema_opt.cota_inf_set(k, -1E50);
    problema_opt.cota_sup_set(k, 1E50);
  end;

 // problema_opt.EstimarMultiplicadores(X,2);

  Randomize;
  X.random();

  X.MasReal(0.001);


  //chequearGrad(X,CFX_Entrenador_f);

  resErr := problema_opt.MaxInBox_Dual(Lambda, X, 1, 1E-6, MaxNIter, cntIters,
    resValCOsto,resValLagrangiana,dfrontera ,flgConvergio, problema_opt.flg_Monitor);


  CFX_Entrenador_f.SetParametrosCerebros(X);

  X.Free;
  CFX_Entrenador_f.Free;
  Result := resValCosto;


end;

function EntrenarCerebro_EBP_ConParsimonia(var aCerebros: TDAOfCerebros;
  aData: TMatOfRecXCF; parsimonia: TParsimonia; paso, momento: NReal;
  var grad_ant: TVectR; var X: TVectR;
  CFX_fEntrenador: TCF_fCerebrosConParsimonia): NReal;
var
  //X: TVectR;
  acum_e2, e2,derr,fDivisor: NReal;
  beta_momento, gamma_momento: NReal;
  NMuestras, nparametros, kMuestra, kPaso, kRec: integer;
  //CFX_fEntrenador: TCF_fCerebrosConParsimonia;
  xRec: TRecXCF;
  aCerebro: TCerebro;
  aRec: TRecXCF;

begin
  beta_momento := momento;
  gamma_momento := 1 - beta_momento;

 // CFX_fEntrenador := TCF_fCerebrosConParsimonia.Create(aCerebros, aData, parsimonia);


  nparametros := parsimonia.dimParsimonia * aCerebros[1].DimParametros;

  NMuestras := length(aData[0]);


  e2 := CFX_fEntrenador.EvalYDarPaso(grad_ant, X, NMuestras, paso, beta_momento,
    gamma_momento);

  Result:=CFX_fEntrenador.f(X);

 // CFX_fEntrenador.SetParametrosCerebros(X); //actualiza los ro y bias de cada cerebro


{


  fDivisor := 1 / (length(aCerebros) * Length(aData[0]));
  acum_e2:=0;

  for kPaso := 1 to high(aCerebros) do
  begin
    derr:=0;
    aCerebro := aCerebros[kPaso];
    aCerebro.BackPropagation_init;
    for kRec := 0 to high(aData[kPaso-1]) do
    begin
     // aCerebro.BackPropagation_init;
      aRec := aData[kPaso - 1][kRec];
      aCerebro.SetEntradas(aRec.X);
      aCerebro.evaluar;
      acum_e2 := acum_e2 + aCerebro.calc_err2(derr, aRec.CF) * fDivisor;
      aCerebro.BackPropagation_ClearErr;
     // aCerebro.BackPropagation_addMuestra(derr);
    end;
  end;
  Result := acum_e2;

                        }

  //X.Free;
 // CFX_fEntrenador.Free;

end;


end.



















