unit uneuronas;

{$mode delphi}
(***
rch@20161101
Definición de clases para manejo de RedesNeuronales.

Este proyecto comenzó como una mejora a los CEGHs pensando en lograr
deformadores que transformen el conjunto de las señales en gaussianas.

En esta unidad se definen las clases: TCerebro, TCapaNeuronas y TNeurona

El TCerebro es un conjunto de TCapaNeuronas conectado a unas ENTRADAS y SALIDAS
Una TCapaNeurona es un agrupamiento de TNeurona
y una TNeurona es un elemento capaz de hacer una combinación lineal de sus
entradas y pasarla por una curva S para formar la salida con Saturación.


****)
interface

uses
  Classes, SysUtils, xMatDefs, matreal, uproblema, uresfxgx, ufxgx, utipos_xcf,
  usigmoide, uauxiliares;

type

  { TNeurona }
  TNeurona = class
    id_str: string; // identificador para debug
    salida: NReal;
    constructor Create(xid_str: string);
    constructor CreateLoadFromFile(var f: textfile);
    procedure StoreInFile(var f: textfile); virtual;
    procedure WriteToConsola; virtual;
    procedure Evaluar; virtual;
    procedure Free; virtual;
    procedure RandomInit(paso: NReal); virtual;
    procedure print_debug(var dbgfile: textfile); virtual;
    procedure conmutarPesosyEntradas(nuevoOrden: TDAofNInt); virtual;
    // devuelve 1 si self > neurona, 0 si son iguales, -1 si neurona > self
    function comparar(neurona: TNeurona): integer; virtual;
  end;

  // Array dinámico de Neuronas
  TDAofNeurona = array of TNeurona;

  // la característica de una neurona de Entrada es que NO tiene entradas,
  // solo una salida que preseta el valor de entrada. Es una bornera inicial
  // para una red neuronal.
  TNeuronaEntrada = class(TNeurona)
  end;


  { TNeuronaConEntradas

    Si el parámetro flg_saturante = TRUE:
      salida = sigmoide( sum_i( ro[i] * entrada[ kOffsetEntrada + i]) + Bias )

    Si el parámetro flg_saturante = FALSE
      salida = sum_i( ro[i] * entrada[ kOffsetEntrada + i]) + Bias

  }


  TNeuronaConEntradas = class(TNeurona)
    ro: TDAofNreal;  // Pesos para combinar las entradaS.
    bias: NReal; // Constante a sumar de la suma ponderada de las entradas
    entradas: TDAOfNeurona;
    flg_saturante: boolean;

    // variables auxiliares para el algoritmo de BackPropagation
    dE_dro: TDAofNreal; // Derivada del Error de la salida de la red (mono_salida)
    dE_dbias: NREal;
    dErr: NReal; // Porción del error que llega a la entrada

    // Crea una Neurona y la conecta a los vectores de Entradas y Salidas pasados
    // como parámetros.
    // xflg_saturante (true por defecto) determina si se aplica la función
    // sigmoide a la combinación lineal de las entradas para calcular la salida o no.
    constructor Create(const xid_str: string; const xEntradas: TDAOfNeurona;
      xflg_saturante: boolean = True);

    // calcula la salida a partir de las entradas
    procedure evaluar; override;

    // Crea la Neurona a partir de la información leída del archivo f
    // y la conecta a los vectores de entradas y salidas.
    constructor CreateLoadFromFile(var f: textfile; xEntradas: TDAofNeurona);

    // Almacena los parámetros de la Neurona en el archivo en forma compatible
    // con la lectura realizada en el constructor CreateFromFile
    procedure StoreInFile(var f: textfile); override;

    procedure WriteToConsola; override;

    // retorna la cantidad de parámetros de la Neurona (length(ro)+1)
    function DimParametros: integer;

    // hace una copia de los parametros en el vector Parametros a partir
    // de la posición kOffset. Al retonrar kOffset está aumentado en la
    // cantidad de parámtros copiados. Esto es útil para hacer una copia
    // de muchas Neuronas en un mismo vector.
    procedure GetParametros(var parametros: TVectR; var kOffset: integer);

    // recupera valores del vector parámetros a partir de la posción kOffset
    // y los copia a los parámetros de la Neurona.
    // El parámetro kOffset se retorna aumentado en la cantidad de parámetros
    // de la Neurona.
    procedure SetParametros(const parametros: TVectR; var kOffset: integer);

    // Acumula en gradParametros los valores dro  a partir de kOffset y
    // retorna kOffset incrementado en la cantidad deparametros de la neurona.
    procedure AcumGradParametros(var gradParametros: TVectR; var kOffset: integer);

    // Libera la memoria reservada por la Neurona.
    procedure Free; override;

    // afecta los pesos y el bias entre  x = x + 2*(random-0.5) * paso
    procedure RandomInit(paso: NReal); override;

    // Debe llamarse antes de iniciar el entrenamiento
    // Borra dro y dbias
    procedure BackPropagation_init;

    // Debe llamarse antes del EVALUAR con cda nueva muestra durante el entrenamiento
    // LIMPIA el acumulador dErr de la neurona
    procedure BackPropagation_ClearErr;

    // Agrega una muestra. Acumula en dro y dbias la componente aportada por la muestra
    // y transmite a las neuronas de entrada la componente de dErr de la muestra.
    procedure BackPropagation_addMuestra;

    // Afecta bias y ro de acuerdo a dbias y dro * (-paso)
    procedure BackPropagation_DarPaso(paso: NReal);

    procedure print_debug(var dbgfile: textfile); override;

    procedure conmutarPesosyEntradas(nuevoOrden: TDAofNInt); override;
    // devuelve 1 si self > neurona, 0 si son iguales, -1 si neurona > self
    // compara los primeros ros y si son iguales los segundos y así hasta el final,
    // si todo es igual compara también los bias
    function comparar(neurona: TNeurona): integer; override;

  end;


  { TCapaNeuronasBase }

  TCapaNeuronasBase = class
    Neuronas: TDAOfNeurona;
    id_str: string;
    constructor Create(xid_str: string; NNeuronas: integer);
    constructor CreateLoadFromFile(var f: textfile);
    procedure StoreInFile(var f: textfile); virtual;
    procedure WriteToConsola; virtual;

    procedure print_debug(var dbgfile: textfile);
    procedure evaluar; virtual;
    procedure RandomInit(paso: NReal);
    procedure Free; virtual;

    // retorna Length( Neuronas )
    function NNeuronas: integer;
  end;

  { TCapaNeuronasEntrada }

  TCapaNeuronasEntrada = class(TCapaNeuronasBase)
    constructor Create(xid_str: string; NNeuronas: integer);
    constructor CreateLoadFromFile(var f: textfile);
  end;


  { TCapaNeuronas
  Una capa de Nueronas está formada por un conjunto de Neuronas y un vector de
  Salida. El vector de Entradas es dado (externo a la Capa).
  En la implementación actual, todas las Neuronas de una Capa tienen como
  entrada el mismo vector de Entradas y cada Neurona controla uno de los casilleros
  de la Salida de la Capa.
  }
  TCapaNeuronas = class(TCapaNeuronasBase)

    // Crea una Capa sobre un vector de Entradas.
    constructor Create(xid_str: string; const xEntradas: TDAofNeurona;
      nNeuronas: integer; flg_saturante: boolean);

    // Recorre las Neuronas para que cada una evalúe su salida.
    procedure evaluar; override;


    // Carga la definición de la Capa desde archivo y la asocia al vector
    // de entradas xEntradas.
    constructor CreateLoadFromFile(var f: textfile; xEntradas: TDAofNeurona);

    // Retorna la cantidad de parámetros de la capa. NNeuronas * ( NEntradas + 1 )
    function DimParametros: integer;

    // hace una copia de los parametros de la capa en el vector Parametros
    // comenzando en el casillero kOffset. El parámetro kOffset se retorna
    // aumentado en la cantidad de elementos copiados.
    procedure GetParametros(var parametros: TVectR; var kOffset: integer);

    procedure AcumGradParametros(var gradParametros: TVectR; var kOffset: integer);


    // fija los parámetros de la Capa a partir del vector Parametros.
    // el parámetro kOffset se retorna aumentado en el cantidad de elementos
    // copiados
    procedure SetParametros(const parametros: TVectR; var kOffset: integer);

    // Ordena las neuronas según los pesos de las entradas y ordena las entradas
    // según cómo se ordenó la capa anterior
    procedure Sort(var variacionDeLaCapaAnterior: TDAofNInt);

    // Libera la memoria reservada por la Capa
    procedure Free; override;

    function PrimerNeurona(out kNeurona: integer): TNeuronaConEntradas;
    function ProximaNeurona(var kNeurona: integer): TNeuronaConEntradas;

    procedure BackPropagation_init;
    procedure BackPropagation_ClearErr;
    procedure BackPropagation_AddMuestra;
    procedure BackPropagation_DarPaso(paso: NReal);

    function SalidasPtr: TDAOfNRealPtr;
  end;

  TDAofCapaNeuronas = array of TCapaNeuronas;

  { TCerebro }

  TCerebro = class
    Entrada_Capa0: TCapaNeuronasEntrada;
    capas: TDAofCapaNeuronas; // ademas de las entradas
    Salida_CapaN: TCapaNeuronas; // apunta capas[high(capas)]

    Premio_UsoInfo_i, Premio_UsoInfo_acum: NReal;
    // Auxiliar para uso algoritmo de distribución del uso de la información.

    constructor Create(nEntradas: integer; // Cantidad de entradas
      xCntNeuronasPorCapa: TDAofNInt; // Cantidad de capas (sin contar la entrada)
      flg_SaturarCapaSalida: boolean); // Indica si saturar la salida de la última capa.

    constructor CreateLoadFromFile(var f: textfile);
    procedure StoreInFile(var f: textfile);
    procedure StoreInArchi(archi: string);
    procedure WriteToConsola;

    procedure Sort;

    // devuelve la dimensión de un vector capaz de almacenar todos los parámetros
    // de las neuronas.
    function DimParametros: integer;

    // hace una copia de los parametros
    procedure GetParametros(var parametros: TVectR);

    procedure AcumGradParametros(var gradParametros: TVectR);

    // recupera la copia
    procedure SetParametros(const parametros: TVectR);

    // Fija el valor de las entradas
    procedure SetEntradas(X: TVectR);

    // jEntrada = 0 ... NEntraas -1
    procedure SetEntrada(jEntrada: integer; valor: NReal);

    procedure evaluar;

    // Recupera el valor de las salidas en Y. Debe aseguarse que Y.n = NSalidas
    procedure GetSalidas(Y: TVectR);

    // jSalida = 0 ... NSalidas -1
    function GetSalida(jSalida: integer): NReal;


    procedure Free;
    procedure RandomInit(paso: NReal);

    procedure BackPropagation_init;
    procedure BackPropagation_ClearErr;

    procedure BackPropagation_addMuestra(derr: TDAOfNReal); overload;
    procedure BackPropagation_addMuestra(derr: TVectR); overload;

    procedure BackPropagation_DarPaso(paso: NReal);

    procedure print_debug(var dbgfile: textfile);

    // Inicializa las capas intermedias para que transmita cada una
    // una de las entradas hasta la neurona de salida de la última capa
    // y calcula los pesos de la última neurona para minimizar su error
    procedure Iniciar_OptUltimaNeurona(const Data: TDAOfRecXCF);


    // Retorna la cantidad de entradas.
    function NEntradas: integer;

    // Retorna la cantidad de salidas.
    function NSalidas: integer;

    // Retorna la Capa de Entrada
    function CapaEntrada: TCapaNeuronasEntrada;

    // Retorna la Capa de Salida
    function CapaSalida: TCapaNeuronas;


    // Retorna la primer capa con entradas (la capa de salida)
    // en kCapa y kNeurona retorna los índices de la capa y la neurona
    // retornados.
    function PrimerCapaConEntradas(out kCapa: integer): TCapaNeuronas;

    // Retorna la próxima capa (caminando hacia la entrada) o nil
    // si se llegó a la primer capa con entradas:
    function ProximaCapaConEntradas(var kCapa: integer): TCapaNeuronas;

    // retorna la primer neurona con entrada (comienza por la capa de salida)
    function PrimerNeuronaConEntradas(
      out kCapa, kNeurona: integer): TNeuronaConEntradas;

    // retorna la proxima neurona con entradas o NIL si ya llegó al final
    function ProximaNeuronaConEntradas(
      var kCapa, kNeurona: integer): TNeuronaConEntradas;

    // Calcula el Sum( ( salida[k] - y.e(k+1 ) )^2 )/NSalidas
    // Retorna en "e" el vector de error (s - y ) el resultado es (e^T. e)
    // los vectors "e" e "y" deben tener dimensión NSalidas.
    function calc_err2(e, y: TVectR): NReal;


  end;

  { TCF_Entrenador
    ==============
    Esta clase define una Tfx apta para ser la función Costo en un problema
    de optimización para entrenar un aCerebro en base a un conjunto de
    puntos CF(X) dados por aData.
  }

  { TCF_fEntrenador }

  TCF_fEntrenador = class(Tfx)
    Data: TDAOfRecXCF;
    cerebro: TCerebro;
    NeuronaActiva: TNeuronaConEntradas;


    constructor Create(aCerebro: TCerebro; aData: TDAOfRecXCF);

    // Retorna el error cuadrático medio de la salida del aCerebro sobre
    // el conjunto aData.
    function f(const X: TVectR): NReal; override;


    // acumula en grad, el gradiente.
    procedure acum_g(var grad: TVectR; const X: TVectR); override;

    // Fija una neurona como objeto de optimización.
    // Pasando kCapa = -1 se pone a NIL la NeuronaActiva
    // Si NeuronaActiva = NIL el objeto de optimización es todo el Cerebro
    // Retorna TRUE si los parámetros son válidos y FALSE en caso contrario.
    function SetNeuronaActiva(kCapa, kNeurona: integer): boolean;

    procedure Free; override;

  private
    dimDato: integer;
    derr: TDAOfNReal;

  end;



{ TCF_fEntrenadorNeurona }
(*
  TCF_fEntrenadorNeurona = class(Tfx)
    Data: TDAOfRecXCF;
    cerebro: TCerebro;

    constructor Create(aCerebro: TCerebro; aData: TDAOfRecXCF);

    // Retorna el error cuadrático medio de la salida del aCerebro sobre
    // el conjunto aData.
    function f(const X: TVectR): NReal; override;

    // acumula en grad, el gradiente.
    procedure acum_g(var grad: TVectR; const X: TVectR); override;

    procedure SetNeurona(kCapa, kNeurona: integer);

    procedure Free; override;

  private
    dimDato: integer;
    derr: TDAOfNReal;
  end;
  *)


// Intenta mejorar los parámetros de aCerebro para reducir el error de la salida
// en el conjunto de muestras aData.
// La función retorna el error cuadrático medio.
function EntrenarCerebro(var aCerebro: TCerebro; // Cerebro
  aData: TDAOfRecXCF;  // Datos de entrenamiento
  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;


// búsqueda aleatoria
function EntrenarCerebro_rnd(var aCerebro: TCerebro; // Cerebro
  aData: TDAOfRecXCF;  // Datos de entrenamiento
  NItres: integer; // Número máximo de iteracciones
  radio: NReal; out cntExitos: integer): NReal;


// Procesa un conjunto de muestras en orden y aplica el Paso como multiplicador
// del -Gradiente para mejorar muestra a muestra los parámetros.
// Calcula el gradiente por el mecanismo de Error Back Propagation para cada muestra
// retorna el promedio de los errores al cuadrado del conjunto de muestras producidos
// durante el entrenamiento. En base al valor devuelto el usuario debe evaluar el mejor PASO
// a utilizar y si es pertinente o no llamar nuevamente la función con el mismo juego de muestras.
function EntrenarCerebro_EBP(var aCerebro: TCerebro; // Cerebro
  aData: TDAOfRecXCF;  // Datos de entrenamiento
  paso: NReal): NReal;


{$IFDEF NEURONAS_DBG}
var
  dbg_File: TextFile;
{$ENDIF}

implementation

{ TCF_fEntrenadorNeurona }
            (*
constructor TCF_fEntrenadorNeurona.Create(aCerebro: TCerebro; aData: TDAOfRecXCF);
begin
  cerebro := aCerebro;
  Data := aData;
  dimDato := Data[0].X.n;
  setlength(derr, 1);
end;

function TCF_fEntrenadorNeurona.f(const X: TVectR): NReal;
begin

end;

procedure TCF_fEntrenadorNeurona.acum_g(var grad: TVectR; const X: TVectR);
begin

end;

procedure TCF_fEntrenadorNeurona.SetNeurona(kCapa, kNeurona: integer);
begin

end;

procedure TCF_fEntrenadorNeurona.Free;
begin

end;

*)

{ TCF_fEntrenador }

constructor TCF_fEntrenador.Create(aCerebro: TCerebro; aData: TDAOfRecXCF);
begin
  cerebro := aCerebro;
  Data := aData;
  dimDato := Data[0].X.n;
  setlength(derr, 1);
  NeuronaActiva := nil;
end;


function TCF_fEntrenador.f(const X: TVectR): NReal;
var
  acum_e2: NReal;
  kRec: integer;
  aRec: TRecXCF;
  jEntrada: integer;
  CFAprox: NReal;
  kOffset: integer;
begin
  if NeuronaActiva = nil then
    Cerebro.SetParametros(X)
  else
  begin
    kOffset := 0;
    NeuronaActiva.SetParametros(X, kOffset);
  end;

  Cerebro.BackPropagation_init;
  acum_e2 := 0;
  setlength(derr, 1);
  for kRec := 0 to high(Data) do
  begin
    aRec := Data[kRec];
    for jEntrada := 0 to dimDato - 1 do
      Cerebro.SetEntrada(jEntrada, aRec.X.e(jEntrada + 1));
    Cerebro.evaluar;
    CFAprox := Cerebro.Salida_CapaN.Neuronas[0].salida;
    derr[0] := (CFAprox - aRec.CF);

    acum_e2 := acum_e2 + sqr(derr[0]);

    Cerebro.BackPropagation_ClearErr;
    Cerebro.BackPropagation_addMuestra(derr);
  end;
  Result := acum_e2 / length(Data);
end;

procedure TCF_fEntrenador.acum_g(var grad: TVectR; const X: TVectR);
var
  kOffset: integer;
begin
  kOffset := 0;
  if NeuronaActiva = nil then
    Cerebro.AcumGradParametros(grad)
  else
    NeuronaActiva.AcumGradParametros(grad, kOffset);
end;

function TCF_fEntrenador.SetNeuronaActiva(kCapa, kNeurona: integer): boolean;
begin
  if kCapa < 0 then
    NeuronaActiva := nil
  else
  begin
    if (kCapa >= 0) and (kCapa < length(Cerebro.capas)) and
      (kNeurona >= 0) and (kNeurona < Cerebro.capas[kCapa].NNeuronas) then
    begin
      NeuronaActiva := Cerebro.capas[kCapa].Neuronas[kNeurona] as TNeuronaConEntradas;
      Result := True;
    end
    else
    begin
      NeuronaActiva := nil;
      Result := False;
    end;

  end;
end;

procedure TCF_fEntrenador.Free;
begin
  setlength(derr, 0);
end;

{ TCapaNeuronasEntrada }

constructor TCapaNeuronasEntrada.Create(xid_str: string; NNeuronas: integer);
var
  k: integer;
begin
  inherited Create(xid_str, NNeuronas);
  for k := 0 to high(Neuronas) do
    Neuronas[k] := TNeuronaEntrada.Create(id_str + IntToStr(k));
end;

constructor TCapaNeuronasEntrada.CreateLoadFromFile(var f: textfile);
var
  kNeurona: integer;
begin
  inherited CreateLoadFromFile(f);
  for kNeurona := 0 to high(Neuronas) do
    Neuronas[kNeurona] := TNeuronaEntrada.CreateLoadFromFile(f);
end;




{ TNeurona }

constructor TNeurona.Create(xid_str: string);
begin
  inherited Create;
  id_str := xid_str;
end;

constructor TNeurona.CreateLoadFromFile(var f: textfile);
begin
  inherited Create;
  readln(f, id_str);
end;

procedure TNeurona.StoreInFile(var f: textfile);
begin
  writeln(f, id_str);
end;

procedure TNeurona.WriteToConsola;
begin
  writeln(id_str);
end;

procedure TNeurona.Evaluar;
begin
  // NADA
end;

procedure TNeurona.Free;
begin
  inherited Free;
end;

procedure TNeurona.RandomInit(paso: NReal);
begin
  // NADA
end;

procedure TNeurona.print_debug(var dbgfile: textfile);
begin
  writeln(dbgfile, 'Neurona: ' + id_str + ' (' + ClassName + ')', #9,
    'Salida:', #9, Salida);
end;

procedure TNeurona.conmutarPesosyEntradas(nuevoOrden: TDAofNInt);
begin
  // NADA
end;

function TNeurona.comparar(neurona: TNeurona): integer;
begin
  Result := 0;
end;

{ TCapaNeuronasBase }

constructor TCapaNeuronasBase.Create(xid_str: string; NNeuronas: integer);
begin
  inherited Create;
  id_str := xid_str;
  setlength(Neuronas, NNeuronas);
end;

constructor TCapaNeuronasBase.CreateLoadFromFile(var f: textfile);
var
  nNeuronas: integer;
begin
  readln(f, id_str);
  readln(f, nNeuronas);
  Create(id_str, nNeuronas);
  setlength(Neuronas, nNeuronas);
end;

procedure TCapaNeuronasBase.StoreInFile(var f: textfile);
var
  k: integer;
begin
  writeln(f, id_str);
  writeln(f, length(Neuronas));
  for k := 0 to high(Neuronas) do
    Neuronas[k].StoreInFile(f);
end;

procedure TCapaNeuronasBase.WriteToConsola;
var
  k: integer;
begin
  writeln(id_str);
  writeln(length(Neuronas));
  for k := 0 to high(Neuronas) do
    Neuronas[k].WriteToConsola;
end;

procedure TCapaNeuronasBase.print_debug(var dbgfile: textfile);
var
  k: integer;
begin
  writeln(dbgfile, 'Capa: ', id_str);
  for k := 0 to high(Neuronas) do
    Neuronas[k].print_debug(dbgfile);
end;

procedure TCapaNeuronasBase.evaluar;
begin
  // NADA
end;

procedure TCapaNeuronasBase.RandomInit(paso: NReal);
var
  kNeurona: integer;
begin
  for kNeurona := 0 to high(Neuronas) do
    Neuronas[kNeurona].RandomInit(paso);
end;

procedure TCapaNeuronasBase.Free;
var
  k: integer;
begin
  for k := 0 to high(Neuronas) do
    Neuronas[k].Free;
  setlength(Neuronas, 0);
  inherited Free;
end;

function TCapaNeuronasBase.NNeuronas: integer;
begin
  Result := length(Neuronas);
end;



{ TCerebro }

constructor TCerebro.Create(nEntradas: integer; xCntNeuronasPorCapa: TDAofNInt;
  flg_SaturarCapaSalida: boolean);
var
  kCapa: integer;
  entradas: TDAofNeurona;
  flg_saturante: boolean;
begin
  inherited Create;

  Entrada_Capa0 := TCapaNeuronasEntrada.Create('CapaE', nEntradas);
  setlength(capas, length(xCntNeuronasPorCapa));
  entradas := Entrada_Capa0.Neuronas;
  for kCapa := 0 to high(capas) do
  begin
    if kCapa < high(capas) then
      flg_saturante := True
    else
      flg_saturante := flg_SaturarCapaSalida;
    capas[kCapa] := TCapaNeuronas.Create('Capa' + IntToStr(kCapa),
      entradas, xCntNeuronasPorCapa[kCapa], flg_saturante);
    entradas := capas[kCapa].Neuronas;
  end;
  Salida_CapaN := capas[high(capas)];
end;

constructor TCerebro.CreateLoadFromFile(var f: textfile);
var
  nCapas, kCapa: integer;
  entradas: TDAofNeurona;
begin
  inherited Create;
  Entrada_Capa0 := TCapaNeuronasEntrada.CreateLoadFromFile(f);
  readln(f, nCapas);
  SetLength(capas, nCapas); // DV@20180815
  entradas := Entrada_Capa0.Neuronas;
  for kCapa := 0 to high(capas) do
  begin
    capas[kCapa] := TCapaNeuronas.CreateLoadFromFile(f, entradas);
    entradas := capas[kCapa].Neuronas;
  end;
  Salida_CapaN := capas[high(capas)];
end;

procedure TCerebro.StoreInFile(var f: textfile);
var
  nCapas, kCapa: integer;
begin
  Entrada_Capa0.StoreInFile(f);
  nCapas := length(capas);
  writeln(f, nCapas);
  for kCapa := 0 to high(capas) do
    capas[kCapa].StoreInFile(f);
end;

procedure TCerebro.StoreInArchi(archi: string);
var
  f: textfile;
begin
  Assign(f, archi);
  rewrite(f);
  StoreInFile(f);
  closefile(f);

end;

procedure TCerebro.WriteToConsola;
var
  nCapas, kCapa: integer;
begin
  Entrada_Capa0.WriteToConsola;
  nCapas := length(capas);
  writeln(nCapas);
  for kCapa := 0 to high(capas) do
    capas[kCapa].WriteToConsola;
end;

procedure TCerebro.Sort;
var
  kCapa: integer;
  variacionDeLaCapaAnterior: TDAofNInt;
begin
  SetLength(variacionDeLaCapaAnterior, 0);
  for kCapa := 0 to high(capas) do
    capas[kCapa].Sort(variacionDeLaCapaAnterior);
end;

function TCerebro.DimParametros: integer;
var
  res, kCapa: integer;
begin
  res := 0;
  for kCapa := 0 to high(capas) do
    res := res + capas[kCapa].DimParametros;
  Result := res;
end;

procedure TCerebro.GetParametros(var parametros: TVectR);
var
  kOffset, kCapa: integer;
begin
  kOffset := 0;
  for kCapa := 0 to high(capas) do
    capas[kCapa].GetParametros(parametros, kOffset);
end;

procedure TCerebro.AcumGradParametros(var gradParametros: TVectR);
var
  kOffset, kCapa: integer;
begin
  kOffset := 0;
  for kCapa := 0 to high(capas) do
    capas[kCapa].AcumGradParametros(GradParametros, kOffset);
end;


procedure TCerebro.SetParametros(const parametros: TVectR);
var
  kOffset, kCapa: integer;
begin
  kOffset := 0;
  for kCapa := 0 to high(capas) do
    capas[kCapa].SetParametros(parametros, kOffset);
end;

procedure TCerebro.SetEntradas(X: TVectR);
var
  jEntrada: integer;
begin
  for jEntrada := 0 to high(Entrada_Capa0.Neuronas) do
    SetEntrada(jEntrada, X.e(jEntrada + 1));
end;

procedure TCerebro.evaluar;
var
  kCapa: integer;
begin

  for kCapa := 0 to high(capas) do
    capas[kCapa].evaluar;

{$IFDEF NEURONAS_DBG}
  writeln(dbg_File);
{$ENDIF}

end;

procedure TCerebro.GetSalidas(Y: TVectR);
var
  k: integer;
begin
  for k := 1 to Y.n do
    y.pon_e(k, GetSalida(k - 1));
end;

function TCerebro.GetSalida(jSalida: integer): NReal;
begin
  Result := CapaSalida.Neuronas[jSalida].salida;
end;


procedure TCerebro.SetEntrada(jEntrada: integer; valor: NReal);
begin
  Entrada_Capa0.Neuronas[jEntrada].salida := valor;
end;

procedure TCerebro.Free;
var
  kCapa: integer;
begin
  Entrada_Capa0.Free;
  for kCapa := 0 to high(capas) do
    capas[kCapa].Free;
  setlength(capas, 0);
  inherited Free;
end;


procedure TCerebro.RandomInit(paso: NReal);
var
  kCapa: integer;
begin
  for kCapa := 0 to high(capas) do
    Capas[kCapa].RandomInit(paso);
end;

procedure TCerebro.BackPropagation_init;
var
  kCapa: integer;
begin
  for kCapa := 0 to high(capas) do
    Capas[kCapa].BackPropagation_init;
end;

procedure TCerebro.BackPropagation_ClearErr;
var
  kCapa: integer;
begin
  for kCapa := 0 to high(capas) do
    Capas[kCapa].BackPropagation_ClearErr;
end;

procedure TCerebro.BackPropagation_addMuestra(derr: TDAOfNReal);
var
  kNeurona: integer;
  aNeurona: TNeurona;
  kCapa: integer;
begin
  // Cargamos los errores de la última capa
  for kNeurona := 0 to high(Salida_CapaN.Neuronas) do
  begin
    aNeurona := Salida_CapaN.Neuronas[kNeurona];
    if aNeurona is TNeuronaConEntradas then
      (aNeurona as TNeuronaConEntradas).dErr := derr[kNeurona];
  end;
  for kCapa := High(Capas) downto 0 do
    Capas[kCapa].BackPropagation_AddMuestra;
end;


procedure TCerebro.BackPropagation_addMuestra(derr: TVectR);
var
  kNeurona: integer;
  aNeurona: TNeurona;
  kCapa: integer;
begin
  // Cargamos los errores de la última capa
  for kNeurona := 0 to high(Salida_CapaN.Neuronas) do
  begin
    aNeurona := Salida_CapaN.Neuronas[kNeurona];
    if aNeurona is TNeuronaConEntradas then
      (aNeurona as TNeuronaConEntradas).dErr := derr.e(kNeurona + 1);
  end;
  for kCapa := High(Capas) downto 0 do
    Capas[kCapa].BackPropagation_AddMuestra;
end;


procedure TCerebro.BackPropagation_DarPaso(paso: NReal);
var
  kCapa: integer;
begin
  for kCapa := 0 to high(capas) do
    Capas[kCapa].BackPropagation_DarPaso(paso);
end;

procedure TCerebro.print_debug(var dbgfile: textfile);
var
  kCapa: integer;
begin
  Entrada_Capa0.print_debug(dbgfile);
  for kCapa := 0 to high(capas) do
    Capas[kCapa].print_debug(dbgfile);
end;

procedure TCerebro.Iniciar_OptUltimaNeurona(const Data: TDAOfRecXCF);
var
  kCapa, kNeurona: integer;
  jEntrada: integer;
  aNeurona: TNeuronaConEntradas;
  alfa, bias: TVectR;
  bias_s: NReal;
  data_copia: TDAOfRecXCF;
  pesos: TVectR;
  c_min, dc: NReal;

begin
  data_copia := clonar_data_xcf(Data);
  alfa := TVectR.Create_Init(length(Entrada_Capa0.Neuronas));
  bias := TVectR.Create_init(alfa.n);
  for kCapa := 0 to high(Capas) - 1 do
  begin

    for jEntrada := 1 to alfa.n do
    begin
      Escalas_Entrada_j_conjuntoXCF(
        alfa.pv[jEntrada], bias.pv[jEntrada], jEntrada, data_copia);
    end;

    for kNeurona := 0 to high(Capas[kCapa].Neuronas) do
    begin
      aNeurona := Capas[kCapa].Neuronas[kNeurona] as TNeuronaConEntradas;
      aNeurona.bias := bias.e(kNeurona + 1);
      for jEntrada := 0 to high(aNeurona.entradas) do
      begin
        if kNeurona = jEntrada then
          aNeurona.ro[jEntrada] := alfa.e(jEntrada + 1)
        else
          aNeurona.ro[jEntrada] := 0.0;
      end;
    end;
  end;

  alfa.Free;
  pesos := TVectR.Create_Init(length(data_copia));
  pesos.Unos;

  MejorNeurona(alfa, bias_s, c_min, dc, data_copia, pesos);
  pesos.Free;
  aNeurona := Salida_CapaN.Neuronas[0] as TNeuronaConEntradas;
  aNeurona.bias := bias_s;
  for jEntrada := 0 to high(aNeurona.ro) do
    aNeurona.ro[jEntrada] := alfa.e(jEntrada + 1);
  free_data_xcf(data_copia);
end;

// Retorna la cantidad de entradas.
function TCerebro.NEntradas: integer;
begin
  Result := Entrada_Capa0.NNeuronas;
end;

// Retorna la cantidad de salidas.
function TCerebro.NSalidas: integer;
begin
  Result := Salida_CapaN.NNeuronas;
end;

function TCerebro.CapaEntrada: TCapaNeuronasEntrada;
begin
  Result := Entrada_Capa0;
end;

function TCerebro.CapaSalida: TCapaNeuronas;
begin
  Result := Salida_CapaN;
end;

function TCerebro.PrimerCapaConEntradas(out kCapa: integer): TCapaNeuronas;
begin
  if length(capas) > 0 then
  begin
    kCapa := high(capas);
    Result := capas[high(capas)] as TCapaNeuronas;
  end
  else
    Result := nil;

end;

function TCerebro.ProximaCapaConEntradas(var kCapa: integer): TCapaNeuronas;
begin
  Dec(kCapa);
  if kCapa >= 0 then
    Result := capas[kCapa]
  else
    Result := nil;
end;

function TCerebro.PrimerNeuronaConEntradas(out kCapa, kNeurona: integer):
TNeuronaConEntradas;
var
  aCapa: TCapaNeuronas;
begin
  aCapa := PrimerCapaConEntradas(kCapa);
  if aCapa <> nil then
    Result := aCapa.PrimerNeurona(kNeurona)
  else
    Result := nil;
end;

function TCerebro.ProximaNeuronaConEntradas(var kCapa, kNeurona: integer):
TNeuronaConEntradas;
var
  aCapa: TCapaNeuronas;
  aNeurona: TNeuronaConEntradas;
begin
  aCapa := capas[kCapa];
  aNeurona := aCapa.ProximaNeurona(kNeurona);
  if aNeurona <> nil then
    Result := aNeurona
  else
  begin
    aCapa := ProximaCapaConEntradas(kCapa);
    if aCapa <> nil then
      Result := aCapa.PrimerNeurona(kNeurona)
    else
      Result := nil;
  end;
end;

function TCerebro.calc_err2(e, y: TVectR): NReal;
var
  res: NReal;
  k: integer;
  a: NReal;
begin
  res := 0;
  for k := 1 to NSalidas do
  begin
    a := CapaSalida.Neuronas[k - 1].salida - y.e(k);
    e.pon_e(k, a);
    res := res + sqr(a);
  end;
  Result := res / NSalidas;
end;


{ TCapaNeuronas }

constructor TCapaNeuronas.Create(xid_str: string; const xEntradas: TDAofNeurona;
  nNeuronas: integer; flg_saturante: boolean);
var
  kNeurona: integer;
begin
  inherited Create(xid_str, nNeuronas);
  for kNeurona := 0 to high(Neuronas) do
    Neuronas[kNeurona] := TNeuronaConEntradas.Create(
      xid_str + '_N' + IntToStr(kNeurona), xentradas, flg_saturante);
end;

procedure TCapaNeuronas.evaluar;
var
  kNeurona: integer;
begin
  for kNeurona := 0 to high(Neuronas) do
    Neuronas[kNeurona].evaluar;

end;

constructor TCapaNeuronas.CreateLoadFromFile(var f: textfile; xEntradas: TDAofNeurona);
var
  kNeurona: integer;
begin
  inherited CreateLoadFromFile(f);
  for kNeurona := 0 to high(Neuronas) do
    Neuronas[kNeurona] := TNeuronaConEntradas.CreateLoadFromFile(f, xEntradas);
end;


function TCapaNeuronas.DimParametros: integer;
var
  kNeurona, res: integer;
begin
  res := 0;
  for kNeurona := 0 to high(Neuronas) do
    res := res + (Neuronas[kNeurona] as TNeuronaConEntradas).DimParametros;
  Result := res;
end;

procedure TCapaNeuronas.GetParametros(var parametros: TVectR; var kOffset: integer);
var
  kNeurona: integer;
begin
  for kNeurona := 0 to high(Neuronas) do
    (Neuronas[kNeurona] as TNeuronaConEntradas).GetParametros(parametros, kOffset);
end;

procedure TCapaNeuronas.AcumGradParametros(var gradParametros: TVectR;
  var kOffset: integer);
var
  kNeurona: integer;
begin
  for kNeurona := 0 to high(Neuronas) do
    (Neuronas[kNeurona] as TNeuronaConEntradas).AcumGradParametros(
      gradParametros, kOffset);
end;

procedure TCapaNeuronas.SetParametros(const parametros: TVectR; var kOffset: integer);
var
  kNeurona: integer;
begin
  for kNeurona := 0 to high(Neuronas) do
    (Neuronas[kNeurona] as TNeuronaConEntradas).SetParametros(parametros, kOffset);
end;

procedure TCapaNeuronas.Sort(var variacionDeLaCapaAnterior: TDAofNInt);
var
  kNeurona, kNeuronaAux, kNeuronaAMover: integer;
  neuronasAux: TDAofNeurona;
  id_strAuxs: array of string;
begin

  // Primero reacomodo las neuronas según la variación de la capa anterior
  if Length(variacionDeLaCapaAnterior) > 0 then
    for kNeurona := 0 to high(Neuronas) do
      TNeuronaConEntradas(Neuronas[kNeurona]).conmutarPesosyEntradas(
        variacionDeLaCapaAnterior);

  // Reacomodo las neuronas
  // Pongo la primera neurona en la lista de acomodar
  SetLength(neuronasAux, 1);
  SetLength(variacionDeLaCapaAnterior, 1);
  SetLength(id_strAuxs, 1);
  neuronasAux[0] := Neuronas[0];
  variacionDeLaCapaAnterior[0] := 0;
  id_strAuxs[0] := Neuronas[0].id_str;
  kNeuronaAux := 0;
  // acá hay que ordenar según los ros
  for kNeurona := 1 to high(Neuronas) do
  begin
    kNeuronaAux := 0;
    // comparo con los elementos de neuronasAux hasta que la kNeurona sea mayor que alguno
    // o se termine el vector, entonces en kNeuronaAux es que va la kNeurona
    while ((kNeuronaAux < Length(neuronasAux)) and
        (Neuronas[kNeurona].comparar(neuronasAux[kNeuronaAux]) < 0)) do
      Inc(kNeuronaAux);

    // Agrando el vector de las neuronas ordenadas para encajar la nueva
    SetLength(neuronasAux, Length(neuronasAux) + 1);
    SetLength(variacionDeLaCapaAnterior, Length(variacionDeLaCapaAnterior) + 1);
    SetLength(id_strAuxs, Length(id_strAuxs) + 1);

    // Corro un lugar a todas las neuronas que son más chicas que kNeurona
    for kNeuronaAMover := high(neuronasAux) downto kNeuronaAux + 1 do
    begin
      neuronasAux[kNeuronaAMover] := neuronasAux[kNeuronaAMover - 1];
      variacionDeLaCapaAnterior[kNeuronaAMover] := variacionDeLaCapaAnterior[kNeuronaAMover - 1];
      id_strAuxs[kNeuronaAMover] := id_strAuxs[kNeuronaAMover - 1];
    end;

    // Encajo la kNeurona
    neuronasAux[kNeuronaAux] := Neuronas[kNeurona];
    variacionDeLaCapaAnterior[kNeuronaAux] := kNeurona;
    id_strAuxs[kNeuronaAux] := neuronas[kNeurona].id_str;

    // No le cuenten a nadie pero estuve un buen rato pensando cómo hacer esta pavada...
  end;

  // Copio la capa ordenada sobre la original
  Neuronas := neuronasAux;

  // les cambio los nombres para que siempre esten ordenadas en N0, N1,....
  for kNeurona := 0 to high(Neuronas) do
    Neuronas[variacionDeLaCapaAnterior[kNeurona]].id_str := id_strAuxs[kNeurona];

end;



procedure TCapaNeuronas.Free;
var
  kNeurona: integer;
begin
  for kNeurona := 0 to high(Neuronas) do
    Neuronas[kNeurona].Free;
  setlength(Neuronas, 0);
  inherited Free;
end;

function TCapaNeuronas.PrimerNeurona(out kNeurona: integer): TNeuronaConEntradas;
begin
  kNeurona := 0;
  if length(Neuronas) > 0 then
    Result := Neuronas[0] as TNeuronaConEntradas
  else
    Result := nil;

end;


function TCapaNeuronas.ProximaNeurona(var kNeurona: integer): TNeuronaConEntradas;
begin
  Inc(kNeurona);
  if kNeurona < length(Neuronas) then
    Result := Neuronas[kNeurona] as TNeuronaConEntradas
  else
    Result := nil;

end;


procedure TCapaNeuronas.BackPropagation_init;
var
  kNeurona: integer;
  aNeurona: TNeuronaConEntradas;
begin
  for kNeurona := 0 to high(neuronas) do
  begin
    aNeurona := Neuronas[kNeurona] as TNeuronaConEntradas;
    aNeurona.BackPropagation_init;
  end;
end;

procedure TCapaNeuronas.BackPropagation_ClearErr;
var
  kNeurona: integer;
  aNeurona: TNeuronaConEntradas;
begin
  for kNeurona := 0 to high(neuronas) do
  begin
    aNeurona := Neuronas[kNeurona] as TNeuronaConEntradas;
    aNeurona.BackPropagation_ClearErr;
  end;
end;

procedure TCapaNeuronas.BackPropagation_AddMuestra;
var
  kNeurona: integer;
  aNeurona: TNeuronaConEntradas;
begin
  for kNeurona := 0 to high(neuronas) do
  begin
    aNeurona := Neuronas[kNeurona] as TNeuronaConEntradas;
    aNeurona.BackPropagation_addMuestra;
  end;
end;




procedure TCapaNeuronas.BackPropagation_DarPaso(paso: NReal);
var
  kNeurona: integer;
  aNeurona: TNeuronaConEntradas;
begin
  for kNeurona := 0 to high(neuronas) do
  begin
    aNeurona := Neuronas[kNeurona] as TNeuronaConEntradas;
    aNeurona.BackPropagation_DarPaso(paso);
  end;
end;

function TCapaNeuronas.SalidasPtr: TDAOfNRealPtr;
var
  kNeurona: integer;
  res: TDAOfNRealPtr;
begin
  setlength(res, Length(Neuronas));
  for kNeurona := 0 to high(res) do
    res[kNeurona] := @Neuronas[kNeurona].salida;
  Result := res;
end;

constructor TNeuronaConEntradas.Create(const xid_str: string;
  const xEntradas: TDAOfNeurona; xflg_saturante: boolean);
begin
  inherited Create(xid_str);
  entradas := xEntradas;
  flg_saturante := xflg_saturante;
  setlength(ro, length(entradas));
  setlength(dE_dro, length(entradas));
  vclear(dE_dro);
end;

procedure TNeuronaConEntradas.evaluar;
var
  kEntrada: integer;
  a: NReal;
begin
  a := bias;
  for kEntrada := 0 to high(ro) do
    a := a + ro[kEntrada] * Entradas[kEntrada].salida;
  if flg_saturante then
    a := sigmoide(a);
  salida := a;

  {$IFDEF NEURONAS_DBG}
  Write(dbg_File, FloatToStr(salida) + #9);
  {$ENDIF}




end;

constructor TNeuronaConEntradas.CreateLoadFromFile(var f: textfile;
  xEntradas: TDAofNeurona);
var
  n, k: integer;

begin
  inherited CreateLoadFromFile(f);

  entradas := xEntradas;
  readln(f, bias);

  readln(f, k);
  if k = 0 then
    flg_saturante := False
  else
    flg_saturante := True;

  Read(f, n);
  setlength(ro, n);
  for k := 0 to n - 1 do
    Read(f, ro[k]);
  readln(f);

  setlength(dE_dro, length(entradas));
  vclear(dE_dro);
  dE_dbias := 0;
  derr := 0;
end;

function TNeuronaConEntradas.DimParametros: integer;
begin
  Result := 1 + length(ro);
end;

procedure TNeuronaConEntradas.GetParametros(var parametros: TVectR;
  var kOffset: integer);
var
  k: integer;
begin
  parametros.pon_e(kOffset + 1, bias);
  Inc(kOffset);
  for k := 0 to high(ro) do
    parametros.pon_e(kOffset + k + 1, ro[k]);
  Inc(kOffset, length(ro));
end;

procedure TNeuronaConEntradas.SetParametros(const parametros: TVectR;
  var kOffset: integer);
var
  k: integer;
begin
  bias := parametros.e(kOffset + 1);
  Inc(kOffset);
  for k := 0 to high(ro) do
    ro[k] := parametros.e(kOffset + k + 1);
  Inc(kOffset, length(ro));
end;


procedure TNeuronaConEntradas.AcumGradParametros(var gradParametros: TVectR;
  var kOffset: integer);
var
  k: integer;
begin
  gradParametros.acum_e(kOffset + 1, dE_dbias);
  Inc(kOffset);
  for k := 0 to high(dE_dro) do
    gradParametros.acum_e(kOffset + k + 1, dE_dro[k]);
  Inc(kOffset, length(dE_dro));
end;


procedure TNeuronaConEntradas.StoreInFile(var f: textfile);
var
  n, k: integer;
begin
  inherited StoreInFile(f);

  writeln(f, bias);
  if flg_saturante then
    writeln(f, 1)
  else
    writeln(f, 0);

  n := length(ro);
  Write(f, n);
  for k := 0 to n - 1 do
    Write(f, #9, ro[k]);
  writeln(f);
end;

procedure TNeuronaConEntradas.WriteToConsola;
var
  n, k: integer;
begin
  inherited WriteToConsola;
  writeln(bias: 0: 4);
  if flg_saturante then
    writeln(1)
  else
    writeln(0);

  n := length(ro);
  Write(n);
  for k := 0 to n - 1 do
    Write(#9, ro[k]: 0: 4);
  writeln();
end;

procedure TNeuronaConEntradas.Free;
begin
  setlength(ro, 0);
  setlength(dE_dro, 0);
  inherited Free;
end;

procedure TNeuronaConEntradas.RandomInit(paso: NReal);
var
  k: integer;
begin
  for k := 0 to high(ro) do
    ro[k] := ro[k] + 2 * (random - 0.5) * paso;
  bias := bias + 2 * (random - 0.5) * paso;
end;

procedure TNeuronaConEntradas.BackPropagation_init;
begin
  dE_dbias := 0;
  vclear(dE_dro);
  dErr := 0;
end;

procedure TNeuronaConEntradas.BackPropagation_ClearErr;
begin
  dErr := 0;
end;

procedure TNeuronaConEntradas.BackPropagation_addMuestra;
var
  dev: NReal;
  aNeurona: TNeuronaConEntradas;
  k: integer;

begin
  if flg_saturante then
    dev := salida * (1 - salida)
  else
    dev := 1;

  // Pasamos el error de la Salida de la sigmoide a la entrada.
  derr := dev * derr;

  // Acumulamos en las componentes del gradiente de la Neurona
  for k := 0 to high(ro) do
    dE_dro[k] := dE_dro[k] + derr * Entradas[k].salida;
  dE_dbias := dE_dbias + derr;

  // Ahora transmitimos a las neuronas de entrada la componente del error
  // para que puedan ellas ajustar sus gradientes.
  if Entradas[0] is TNeuronaConEntradas then
    for k := 0 to high(Entradas) do
    begin
      aNeurona := Entradas[k] as TNeuronaConEntradas;
      aNeurona.dErr := aNeurona.dErr + derr * ro[k];
    end;
end;

procedure TNeuronaConEntradas.BackPropagation_DarPaso(paso: NReal);
var
  k: integer;
begin
  for k := 0 to high(ro) do
    ro[k] := ro[k] - paso * dE_dro[k];
  bias := bias - paso * dE_dbias;
end;

procedure TNeuronaConEntradas.print_debug(var dbgfile: textfile);
var
  k: integer;
begin
  inherited print_debug(dbgfile);

  writeln(dbgfile, #9, 'derr:', #9, dErr);
  Write(dbgfile, #9, 'ro:');
  for k := 0 to high(ro) do
    Write(dbgfile, #9, ro[k]);
  writeln(dbgfile, #9, 'bias:', #9, bias);

  Write(dbgfile, #9, 'dE_dro:');
  for k := 0 to high(ro) do
    Write(dbgfile, #9, dE_dro[k]);
  writeln(dbgfile, #9, 'dE_dbias:', #9, dE_dbias);

  Write(dbgfile, #9, 'Entradas:');
  for k := 0 to high(entradas) do
    Write(dbgfile, #9, entradas[k].id_str);
  writeln(dbgfile);
  writeln(dbgfile);
end;

procedure TNeuronaConEntradas.conmutarPesosyEntradas(nuevoOrden: TDAofNInt);
var
  kRo: integer;
  roAux: TDAofNReal;
  entradasAux: TDAofNeurona;
begin
  SetLength(roAux, Length(ro));
  SetLength(entradasAux, Length(ro));

  for kRo := 0 to high(ro) do
  begin
    roAux[kRo] := ro[nuevoOrden[kRo]];
    entradasAux[kRo] := entradas[nuevoOrden[kRo]];
  end;

  for kRo := 0 to high(ro) do
  begin
    ro[kRo] := roAux[kRo];
    entradas[kRo] := entradasAux[kRo];
  end;

  SetLength(roAux, 0);
  SetLength(entradasAux, 0);
end;

function TNeuronaConEntradas.comparar(neurona: TNeurona): integer;
var
  comparando: boolean;
  kRo, res: integer;
  aneurona: TNeuronaConEntradas;
begin

  aneurona := neurona as TNeuronaConEntradas;



  comparando := True;
  kRo := 0;
  res := 0;

  while comparando do
  begin
    // comparo los coeficientes
    if self.ro[kRo] > aneurona.ro[kRo] then
    begin
      comparando := False;
      res := 1;
    end
    else if self.ro[kRo] < aneurona.ro[kRo] then
    begin
      comparando := False;
      res := -1;
    end;
    Inc(kRo);
    // si los coeficientes dieron todos iguales al llegar al final, comparo el bias
    if kRo >= Length(self.ro) then
    begin
      comparando := False;
      if self.bias > aneurona.bias then
        res := 1
      else if self.bias < aneurona.bias then
        res := -1;
    end;
  end;
  Result := res;
end;


function EntrenarCerebro(var aCerebro: TCerebro; // Cerebro
  aData: TDAOfRecXCF;  // Datos de entrenamiento
  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
  CFX_fEntrenador: TCF_fEntrenador;
  nParametros: integer;

  X, Xs: TVectR;
  problema_opt: TProblema_m01;
  k: integer;
  resValCosto, resValLagrangiana: NReal;
  resErr: NReal;
  kOffset: integer;

begin
  CFX_fEntrenador := TCF_fEntrenador.Create(aCerebro, aData);
  CFX_fEntrenador.SetNeuronaActiva(kCapa, kNeurona);

  if CFX_fEntrenador.NeuronaActiva = nil then
    nparametros := aCerebro.DimParametros
  else
    nparametros := CFX_fEntrenador.NeuronaActiva.DimParametros;

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

  if CFX_fEntrenador.NeuronaActiva = nil then
    aCerebro.GetParametros(X)
  else
  begin
    kOffset := 0;
    CFX_fEntrenador.NeuronaActiva.GetParametros(X, kOffset);
  end;

  writeln('Inicio Opt por Problema ... ');

  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, -10000);
    problema_opt.cota_sup_set(k, 10000);
  end;

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

  if CFX_fEntrenador.NeuronaActiva = nil then
    aCerebro.SetParametros(X)
  else
  begin
    kOffset := 0;
    CFX_fEntrenador.NeuronaActiva.SetParametros(X, kOffset);
  end;

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

end;


function EntrenarCerebro_rnd(var aCerebro: TCerebro; aData: TDAOfRecXCF;
  NItres: integer; radio: NReal; out cntExitos: integer): NReal;

var
  CFX_fEntrenador: TCF_fEntrenador;
  nParametros: integer;
  g, X, Xs: TVectR;
  k, j: integer;
  cx, cxs: NReal;

begin
  nparametros := aCerebro.DimParametros;
  X := TVectR.Create_init(nparametros);
  Xs := TVectR.Create_init(nparametros);
  g := TVEctR.Create_init(nparametros);
  CFX_fEntrenador := TCF_fEntrenador.Create(aCerebro, aData);

  aCerebro.GetParametros(X);
  cx := CFX_fEntrenador.f(X);
  g.Ceros;
  CFX_fEntrenador.acum_g(g, X);

  cntExitos := 0;

  for k := 1 to NItres do
  begin
    for j := 1 to X.n do
      Xs.pon_e(j, X.e(j) - g.e(j) * radio * (random - 0.2));
    cxs := CFX_fEntrenador.f(Xs);
    if cxs < cx then
    begin
      vswap(X, Xs);
      vswap(cx, cxs);
      g.Ceros;
      CFX_fEntrenador.acum_g(g, X);
      Inc(cntExitos);
    end;
  end;

  aCerebro.SetParametros(X);
  X.Free;
  Xs.Free;
  g.Free;
  CFX_fEntrenador.Free;
  Result := cx;
end;

function EntrenarCerebro_EBP(var aCerebro: TCerebro; aData: TDAOfRecXCF;
  paso: NReal): NReal;
var
  kMuestra: integer;
  xRec: TRecXCF;
  e: TVectR;
  e2: NReal;

begin
  e := TVectR.Create_Init(aCerebro.NSalidas);
  e2 := 0;
  for kMuestra := 0 to high(aData) do
  begin
    xRec := aData[kMuestra];
    aCerebro.SetEntradas(xRec.X);
    aCerebro.BackPropagation_init;
    aCerebro.evaluar;
    e2 := e2 + aCerebro.calc_err2(e, xRec.Y);
    aCerebro.BackPropagation_addMuestra(e);
    aCerebro.BackPropagation_DarPaso(paso);
  end;
  Result := e2 / length(aData);
  e.Free;
end;



end.
