unit uneuronas;
{$DEFINE SIGMOIDEDEVMIN}

{$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, Math, xMatDefs, matreal, uproblema, uresfxgx, ufxgx, utipos_xcf,
  usigmoide, utanh, uauxiliares;


// 0 : no satura; 1: tanh ; 2: sigmoide;  3: logica ; 4: softmax
type
  TTipoSaturacion = (
    CTS_Lineal,
    CTS_Tanh,
    CTS_Sigmoide,
    CTS_Logica, // x > 0
    CTS_ReLu, // ( x > 0 ) x
    CTS_Max, // = max(ro_i x_i )
    CTS_PonderSoftmax, // = sum w_i x_i ; donde w_i = exp( ro_i x_i ) / sum ( exp( ro_i x_i ) )
    CTS_Softmax // para neuronas de una CapaSoftMax
    );

  TDAOfTipoSaturacion = array of TTipoSaturacion;

type
  TCapaNeuronasBase = class; // se define más adelante.

  { TNeurona }
  TNeurona = class
    id_str: string; // identificador para debug
    capa: TCapaNeuronasBase;

    salida: NReal;

    // auxiliar para cálculo de derivadas. Variación en la entrada
    dErr: NReal; // Porción del error que llega a la entrada

    constructor Create( xid_str: string; capa: TCapaNeuronasBase );
    constructor CreateFromBin(f: TStream; capa: TCapaNeuronasBase );
    constructor CreateFromXLT(f: TStream; capa: TCapaNeuronasBase );

    procedure WriteToBin(f: TStream); virtual;
    procedure WriteToXLT(f: TStream); virtual;
    procedure WriteToConsola; virtual;
    procedure Evaluar; virtual;
    destructor destroy; override;
    procedure RandomInit(paso: NReal); virtual;
    procedure print_debug(var dbgfile: textfile); virtual;
    procedure conmutarPesosyEntradas(nuevoOrden: TDAofNInt); virtual;

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

  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 }

  TNeuronaEntrada = class(TNeurona)
    x, bias, ro: NReal;  // salida = bias + ro * x
    constructor Create(xid_str: string; capa: TCapaNeuronasBase);
    constructor CreateFromBin(f: TStream; capa: TCapaNeuronasBase);
    constructor CreateFromXLT(f: TStream; capa: TCapaNeuronasBase);
    procedure WriteToBin(f: TStream); override;
    procedure WriteToXLT(f: TStream); override;
    procedure WriteToConsola; override;
    procedure Evaluar; override;
  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;
    tipoSaturacion: TTipoSaturacion;

    tipoEntrenamiento: integer; // por defecto está en 1 y participa del entrenamiento
    // si se pone a 0 deja de participar del entrenamiento.
    // En el caso de las Neuronas con saturación Max o PonderSoftMax
    // los ro se ponen a 1 y tipo_entrenamiento = 0 para que no intente
    // modificar los ro.

    // variables auxiliares para el algoritmo de BackPropagation
    dE_dro_: TDAofNreal; // Derivada del Error de la salida de la red
    dE_dbias_: NREal;

    // Valores del paso anterior para aplicar el "momentum".
    dE_dro_ant: TDAofNreal; // Derivada del Error de la salida de la red
    dE_dbias_ant: NREal;

    // auxiliares para casos de saturación especial Max y SoftMax
    kOfMax: integer; // enrada seleccionada al evaluar
    auxSoftMaxProd: TDAOfNreal;
    // Producto de las entradas por los pesos se calcula al evaluar

    // 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;
      capa: TCapaNeuronasBase;
      const xEntradas: TDAOfNeurona;
      xTipoSaturacion: TTipoSaturacion; tipoEntrenamiento: integer);

    // 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 CreateFromBIN(f: TStream; xEntradas: TDAofNeurona; capa: TCapaNeuronasBase );
    constructor CreateFromXLT(f: TStream; xEntradas: TDAofNeurona; capa: TCapaNeuronasBase );

    // Almacena los parámetros de la Neurona en el archivo en forma compatible
    // con la lectura realizada en el constructor CreateFromFile
    procedure WriteToBin(f: TStream); override;
    procedure WriteToXLT(f: TStream); 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.
    destructor destroy; 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;


    // 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;


    // Solo propaga dErr hacia las entradas para cálculo del gradiente de la salida
    // respecto de la entrada
    procedure BackPropagation_gradX;


    // Traduce el error a la entrada de la neurona multiplicando por la derivada de la función
    // saturante.
    procedure BackPropagation_TrauducirErrorALaEntrada;

    // Afecta bias y ro de acuerdo a dbias y dro / NMuestras * (-paso)
    // se debe cumplir beta_momento + gamma_momento = 1
    // El gradiente aplciado grad = beta_momento * grad_anterior + gamma_momento * grad_delPaso
    procedure BackPropagation_DarPaso(NMuestras: integer; paso, beta_momento,
      gamma_momento, alfa_regularizacion: NReal);

    procedure print_debug(var dbgfile: textfile); override;

    // intercambia las referencias a las neuronas k y j de la entrada;
    procedure IntercambiarReferenciasANeuronas( k, j: integer );


    // Imprime una fórmula como para generar un script de expresiones
    function GetFormula: string;
  end;


  { TCapaNeuronasBase }

  TCapaNeuronasBase = class
    Neuronas: TDAOfNeurona;
    id_str: string;

    constructor Create(xid_str: string; NNeuronas: integer);
    constructor CreateFromBin(f: TStream);
    constructor CreateFromXLT(f: TStream);

    procedure WriteToBin(f: TStream); virtual;
    procedure WriteToXLT(f: TStream); virtual;
    procedure WriteToConsola; virtual;

    procedure print_debug(var dbgfile: textfile);
    procedure evaluar; virtual;
    procedure RandomInit(paso: NReal);
    destructor destroy; override;

    procedure BackPropagation_ClearErr;

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

    // Agrega una neurona del tipo especificado y la contecta a todas las Neuronas de la capa anterior.
    // Fija los parámetros de la neurona de forma que intenta ser NEUTRA según el tipo de las neuronas
    // de la capa siguiente.
    function AgregarNeuronaNeutra( CapaSiguiente: TCapaNeuronasBase; tipoSaturacion: TTipoSaturacion ): TNeurona;
  end;

  { TCapaNeuronasEntrada }

  TCapaNeuronasEntrada = class(TCapaNeuronasBase)
    constructor Create(xid_str: string; NNeuronas: integer);
    constructor CreateFromBin(f: TStream);
    constructor CreateFromXLT(f: TStream);
  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)
     entradas: TDAOfNeurona; // referencia a la capa de entrada

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

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

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

    // 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); virtual;

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

    // 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); virtual;

    // Ordena las neuronas según los pesos de las entradas y notifica los cambios
    // a la capa siguiente.
    procedure Sort( capaSiguiente: TCapaNeuronas ); virtual;


    // Libera la memoria reservada por la Capa
    destructor destroy; override;

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

    procedure BackPropagation_init; virtual;
    procedure BackPropagation_AddMuestra; virtual;

    procedure BackPropagation_DarPaso(NMuestras: integer; paso, beta_momento,
      gamma_momento, alfa_Regularizacion: NReal); virtual;

    // propaga dErr hacia las entradas para cálculo del gradiente de la salida
    // respecto de la entrada
    procedure BackPropagation_gradX; virtual;

    function SalidasPtr: TDAOfNRealPtr; virtual;

    // recorre las Neuronas de la capa y cambia las referencias a las neuronas
    // k y j de la capa anterior.
    function IntercambiarReferenciasANeuronas( k, j: integer ): boolean; virtual;
  end;



   (**

    una CapaSoftmax, no entrena, simplemente hace el Softmax de las salidas de la capa anterior
    y la presenta en sus neuronas. Tiene tantas salidas como entradas.
   **)

    { TCapaSoftmax }

    TCapaSoftmax = class(TCapaNeuronas)

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

      // Retorna la cantidad de parámetros de la capa.
      // ( CERO, no tiene parámetros de entrenamiento )
      function DimParametros: integer; override;

      // no hace nada
      procedure GetParametros(var parametros: TVectR; var kOffset: integer); override;

      // no hace nada.
      procedure AcumGradParametros(var gradParametros: TVectR; var kOffset: integer); override;


      // no hace nada.
      procedure SetParametros(const parametros: TVectR; var kOffset: integer); override;

      // no hace nada.
      procedure Sort( capaSiguiente: TCapaNeuronas ); override;


      procedure evaluar; override;

      procedure BackPropagation_AddMuestra; override;

      procedure BackPropagation_DarPaso(NMuestras: integer; paso, beta_momento,
        gamma_momento, alfa_Regularizacion: NReal); override;

      // propaga dErr hacia las entradas para cálculo del gradiente de la salida
      // respecto de la entrada
      procedure BackPropagation_gradX; override;

    end;

  TDAofCapaNeuronas = array of TCapaNeuronas;

  { TCerebro }

  TCerebro = class
    Entrada_Capa0: TCapaNeuronasEntrada;
    capas: TDAofCapaNeuronas; // ademas de las entradas
    Salida_CapaN: TCapaNeuronas; // apunta capas[high(capas)]
    flg_YMax, flg_YMin: TDAOfBoolean; // indica si se aplica saturación de la salida
    YMax, YMin: TDAOfNReal; // fija los valores de saturación en caso de aplicarse.

    // se pone a Cero con BackPropagation_init y se incrementa con cada muestra
    // agregada.
    BackPropagation_MuestrasAcumuladas: integer;

    // Impone CTS_Sigmoide
    constructor Create(
       nEntradas: integer; xCntNeuronasPorCapa: TDAofNInt; flg_saturarUltimaCapa: boolean ); overload;

    constructor Create(nEntradas: integer; xCntNeuronasPorCapa: TDAofNInt;
      tipoSaturacionSaturacionPenultimaCapa,
  tipoSaturacionCapaSalida: TTipoSaturacion; tipoSatDefault: TTipoSaturacion=
  CTS_Tanh; flg_AgregarUnaLinealEnTodasLasCapasIntermedias: boolean = false ); overload;

    // Indica si saturar la salida de la última capa
    constructor Create(
      nEntradas: integer;
      xCntNeuronasPorCapa: TDAofNInt;
      tiposSaturacionCapa: TDaOfTipoSaturacion;
      flg_AgregarUnaLinealEnTodasLasCapasIntermedias: boolean = false ); overload;


    constructor CreateFromArchi(archi: string);
    constructor CreateFromBin(f: TSTream);
    constructor CreateFromXLT(f: TSTream);

    procedure WriteToBin(f: TStream);
    procedure WriteToXLT(f: TStream);

    procedure WriteToArchi(archi: string);
    procedure WriteToConsola;

    function Clonar: TCerebro;

    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);

    // y le agrega un ruido multiplicando cada entrada por ( 1+ prnd * 2 ( random - 1 ) )
    procedure SetEntradas_prnd(X: TVectR; prnd: NReal );


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

    // Impone un máximo a la salida jSalida: 0 ... NSlidas-1
    procedure SetMaxSalida( jSalida: integer; v_Max: NReal );
    // Impone un mínimo a la salida jSalida: 0 ... NSlidas-1
    procedure SetMinSalida( jSalida: integer; v_Min: 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;

    destructor destroy; override;
    procedure RandomInit(paso: NReal);

    procedure BackPropagation_init;
    procedure BackPropagation_ClearErr;

    procedure BackPropagation_addMuestra(derr: TDAOfNReal); overload;
    procedure BackPropagation_addMuestra(derr: TVectR); overload;
    procedure BackPropagation_addMuestra(derr: NReal); overload; // una sola salida

    // calcula el gradiente de la salida kSalida respecto de las entradas
    // en el último valor evaluado de la red.
    // Para realizar el cálculo utiliza BackPropagation_ClearErr
    // Evaluado un punto, se puede llamar este método con kSalida = 0, ...NSalias -1
    // para obtener cada uno de los gradientes.
    // Para leer el resultado debe usar el proc. GetGradX
    // kSalida = 0, ... , NSalidas - 1
    procedure CalcGradX(kSalida: integer);
    procedure GetGradX(gradX: TDAOfNReal); overload;
    procedure GetGradX(gradX: TVectR); overload;


    procedure BackPropagation_DarPaso(paso, beta_momento, gamma_momento,
      alfa_regularizacion: NReal);

    procedure print_debug(var dbgfile: textfile); overload;
    procedure print_debug(const archi: string); overload;

    // 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; overload;
    function calc_err2( var e: NReal; y: NReal): NReal; overload;

    // Las neuronas de Entrada tienen un bias y un único ro para permitir
    // un escalamiento de las entradas en la propia Neurona.
    // El procedmiento EscalarEntradas, fija el bías de cada neurona a menos la
    // semisuma entre el valor mínimo y máximo de cada entrada dividido por dx
    // y el ro al valor 1/dx; donde dx = ( xmax - xmin )/2
    // semisuma de la diferencia de forma tal que cuando la entrada toma el valor
    // máximo la salida de la Neurona es 1 y cuando la entrada toma el valor mínimo
    // su valor es -1.
    // Estos parámetros de la capa de entrada son FIJOS; no son objeto de optimización
    // del Cerebro. Deben considerarse como un pre-acondiconamiento de los datos de
    // entrada.
    procedure EscalarEntradas(aData: TDAOfRecXCF);  // Datos de entrenamiento

    // El procedimiento EscalarCapaSalida, supone que la última capa NO-Satura y
    // fija el valor del bías a la semisuma ( ymax + ymin )/2 y fija todos los
    // ro al valor ( ymax - ymin )/(2 NSalidas) de esta forma si todas las Neuronas
    // de la entrada de una de salida tienen valor 1 la salida alcanza el máximo
    // y si todas tienen -1 alcanzan el mínimo. Los parámetros bias y ro de la
    // capa de salida son objeto de la optimización y por tanto EscalarCapaSalida
    // solo es una ayuda a poner parámetros iniciales coherentes con que las capas
    // ocultas tienen salidas de -1 a 1.
    procedure EscalarCapaSalida(aData: TDAOfRecXCF);  // Datos de entrenamiento


     // intercambia la posición de las neuronas hNeurona y jNeurona de la capa
     // kCapa; acomodando las referencias y los ro de la capa siguiente.
     procedure IntercambiarNeuronas( kCapa, hNeurona, jNeurona: integer );

  end;

  TDAOfCerebros = array of TCerebro;


  { 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;

    destructor destroy; override;

  private
    dimDato: integer;
    derr: TVectR;
  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;

(**
para debug, calcula el gradiente por BackPrpagation y por incrementos
en la muestra kMuestra.
**)
procedure TestGrad_BP(var aCerebro: TCerebro; aData: TDAOfRecXCF; kMuestra: integer);

// 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.
// El parámetro momento, le da inercia al gradiente. Si Momento = 0 no hay inercia.
// Si Momento = 1, no hay actualización del gradiente.
function EntrenarCerebro_EBP(var aCerebro: TCerebro; // Cerebro
  aData: TDAOfRecXCF;  // Datos de entrenamiento
  paso, momento, alfa_Regularizacion: NReal): NReal;


function EntrenarCerebro_EBP_minibatch(
  // var grad: TVectR;
  var aCerebro: TCerebro; // Cerebro
  aData: TDAOfRecXCF;  // Datos de entrenamiento
  paso, momento, alfa_Regularizacion: NReal; NMuestras: integer;
  // si NMuestras <= 0 ejecuta todas las muestras una vez.
  semilla: integer;
  prnd: NReal = 0.0;  // porcion de ruido para afectar las entradas
  jSalidaPesosPorRango: integer = 1; // 1 .. NSalidas
  pesosPorRango: TDAofNReal = nil; // Si la dimensión es > 1, se consideran como Pesos
    // para priorizar el error cuadrático según el rango de la salida.
  ymin: NReal = 0.0; dy: NReal = 1.0
    // Para determinar el rango al que pertenece la salida, se considera
    // kRango = min( trunc( ( Y - Ymin ) / dy ), high( pesosPorRango ) )
  ): NReal;


// en la primera fila guarda encabezados x1, x2, ... xn, y1, ...ym, Y1, ... Ym, param1, param2, ... paramP
// separados por tabuladores
// A continuación en cada fila se escriben los datos, la salida y el gradiente del error respecto
// de los parámetros.
procedure CalcularYGuardar(archi: string; const aCerebro: TCerebro; aData: TDAOfRecXCF);



function StrToTipoSaturacion (TipoSat_str_: string): TTipoSaturacion;

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



// Supone que a_ y b_ son instancias de TNeuronaConEntradas
// Retorna -1 si a < b, 0 si a = b y +1 si a > b
function CompareNeuronas( a_, b_: pointer ): integer;

implementation

{ TCapaSoftmax }

constructor TCapaSoftmax.Create(xid_str: string; const xEntradas: TDAofNeurona;
  nNeuronas: integer);
begin
  inherited Create(xid_str, xEntradas, nNeuronas, CTS_Softmax, 0, false );
 end;

function TCapaSoftmax.DimParametros: integer;
begin
  result:= 0;
end;

procedure TCapaSoftmax.GetParametros(var parametros: TVectR;
  var kOffset: integer);
begin
  // nada
end;

procedure TCapaSoftmax.AcumGradParametros(var gradParametros: TVectR;
  var kOffset: integer);
begin
  // nada
end;

procedure TCapaSoftmax.SetParametros(const parametros: TVectR;
  var kOffset: integer);
begin
  // nada
end;

procedure TCapaSoftmax.Sort(capaSiguiente: TCapaNeuronas);
begin
 // nada
end;

procedure TCapaSoftmax.evaluar;
var
  k: integer;
  x: NReal;
  aNeuronaEntrada, aNeurona: TNeurona;
  valSumatoria: NReal;

begin
  // no llamamos el evaluar de las neuronas, calculamos y le imponemos el resultado.
  valSumatoria:= 0;
  for k:= 0 to High( Entradas ) do
  begin
    aNeuronaEntrada:= Entradas[k];
    x:= exp( aNeuronaEntrada.Salida );
    valSumatoria:= valSumatoria + x;
    aNeurona:= Neuronas[k];
    aNeurona.salida:= x;
  end;
  for k:= 0 to high( Neuronas ) do
  begin
    aNeurona:= Neuronas[k];
    aNeurona.salida:= aNeurona.salida/ valSumatoria;
  end;
end;


procedure TCapaSoftmax.BackPropagation_AddMuestra;
var
  aNeuronaEntrada, aNeurona_j, aNeurona_k: TNeurona;
  k, j: integer;
  dErr: NReal;
begin
  for k := 0 to high(Neuronas) do
  begin
    aNeurona_k:= Neuronas[k];
    for j:= 0 to high( Entradas ) do
    begin
      dErr:= aNeurona_k.dErr;
      aNeuronaEntrada := Entradas[j];
      aNeurona_j := Neuronas[j];

      if k <> j then
         dErr:= -aNeurona_k.Salida *  aNeurona_j.Salida * dErr
      else
         dErr:=  aNeurona_k.salida * ( 1 - aNeurona_j.Salida ) * dErr;
      aNeuronaEntrada.dErr := aNeuronaEntrada.dErr + dErr;
    end;
  end;

end;

procedure TCapaSoftmax.BackPropagation_DarPaso(NMuestras: integer; paso,
  beta_momento, gamma_momento, alfa_Regularizacion: NReal);
begin
  // no hace nada
end;

procedure TCapaSoftmax.BackPropagation_gradX;
begin
  // en este caso, hal no haber parámetros optimizables es lo mismo que AddMuestra
  BackPropagation_AddMuestra;
end;


{ TNeuronaEntrada }

constructor TNeuronaEntrada.Create(xid_str: string; capa: TCapaNeuronasBase);
begin
  inherited Create(xid_str, Capa);
  x:= 0;
  bias := 0;
  ro := 1;
end;

constructor TNeuronaEntrada.CreateFromBin(f: TStream ; capa: TCapaNeuronasBase );
begin
  inherited CreateFromBin(f, capa );
  f.Read(bias, sizeOf(bias));
  f.Read(ro, sizeOf(ro));
end;

constructor TNeuronaEntrada.CreateFromXLT(f: TStream ; capa: TCapaNeuronasBase);
var
  s: string;
begin
  inherited CreateFromXlt(f, capa );
  readlnS(f, s);
  bias := NextFloat(s, #9);
  ro := NextFloat(s, #9);
end;

procedure TNeuronaEntrada.WriteToBin(f: TStream);
begin
  inherited WriteToBin(f);
  f.Write(bias, sizeOf(bias));
  f.Write(ro, sizeOf(ro));
end;

procedure TNeuronaEntrada.WriteToXLT(f: TStream);
var
  s: string;
begin
  inherited WriteToXlt(f);
  s := FloatToStr(bias) + #9 + FloatToStr(ro);
  WritelnS(f, s);
end;

procedure TNeuronaEntrada.WriteToConsola;
begin
  inherited WriteToConsola;
  writeln('bias: ', bias, ', ro: ', ro);
end;

procedure TNeuronaEntrada.Evaluar;
begin
  salida := bias + ro * x;
end;

{ 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;
  derr := TVectR.Create_Init(aCerebro.NSalidas);
  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;

  for kRec := 0 to high(Data) do
  begin
    aRec := Data[kRec];
    Cerebro.SetEntradas(aRec.x);
    Cerebro.evaluar;
    acum_e2 := acum_e2 + Cerebro.calc_err2(derr, aRec.Y);
    (*
    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;

destructor TCF_fEntrenador.destroy;
begin
  derr.Free;
  inherited destroy;
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), self );
end;

constructor TCapaNeuronasEntrada.CreateFromBin(f: TStream);
var
  clase: string;
  k: integer;
begin
  inherited CreateFromBin(f);

  for k := 0 to high(Neuronas) do
  begin
    clase:= f.ReadAnsiString;
    if clase = TNeuronaEntrada.ClassName then
       Neuronas[k]:= TNeuronaEntrada.CreateFromBin( f, self  )
    else
       raise Exception.Create( 'Error, TCapaNeuronasEntrada, clase de neurona desconocida: '+ clase );
  end;

end;

constructor TCapaNeuronasEntrada.CreateFromXLT(f: TStream);
var
  k: integer;
  clase: string;
begin
  inherited CreateFromXLT(f);
  for k := 0 to high(Neuronas) do
  begin
    readlnS(f, clase );
    if clase = TNeuronaEntrada.ClassName then
       Neuronas[k]:= TNeuronaEntrada.CreateFromXLT( f, self  )
    else
       raise Exception.Create( 'Error, clase de neurona desconocida: '+ clase );
  end;
end;




{ TNeurona }

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

constructor TNeurona.CreateFromBin(f: TStream; capa: TCapaNeuronasBase);
var
  n: integer;
begin
  inherited Create;
  self.capa:= capa;
  f.Read(n, sizeof(n));
  setlength(id_str, n);
  f.Read(id_str[1], n);
end;

constructor TNeurona.CreateFromXLT(f: TStream; capa: TCapaNeuronasBase);
begin
  inherited Create;
  self.capa:= capa;
  readlnS(f, id_str);
end;

procedure TNeurona.WriteToBin(f: TStream);
var
  n: integer;
begin
  n := length(id_str);
  f.Write(n, Sizeof(n));
  f.Write(id_str[1], n);

end;

procedure TNeurona.WriteToXLT(f: TStream);
begin
  writelnS(f, id_str);
end;

procedure TNeurona.WriteToConsola;
begin
  writeln(id_str, #9, ClassName );
end;

procedure TNeurona.Evaluar;
begin
  // NADA
end;

destructor TNeurona.destroy;
begin
  inherited destroy;
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;


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


{ TCapaNeuronasBase }

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


constructor TCapaNeuronasBase.CreateFromXLT(f: TStream);
var
  nNeuronas: integer;
  s: string;
begin
  readlnS(f, id_str);
 // readlnS(f, s);
  readlnS(f, s);
  nNeuronas := StrToInt(s);
  Create(id_str, nNeuronas);

end;

constructor TCapaNeuronasBase.CreateFromBin(f: TStream);
var
  nNeuronas: integer;
  n: integer;
  Clase: ansistring;
  k: integer;

begin
  f.Read(n, sizeOf(n));
  setlength(id_str, n);
  f.Read(id_str[1], n * sizeOF(id_str[1]));
  f.Read(nNeuronas, SizeOF(nNeuronas));
  Create(id_str, nNeuronas);
  setlength(Neuronas, nNeuronas);
end;


procedure TCapaNeuronasBase.WriteToXLT(f: TStream);
var
  k: integer;
begin
  writelnS(f, id_str);
  writelnS(f, IntToStr(length(Neuronas)));
  for k := 0 to high(Neuronas) do
  begin
    writelnS( f, Neuronas[k].ClassName );
    Neuronas[k].WriteToXLT(f);
  end;
end;

procedure TCapaNeuronasBase.WriteToBin(f: TStream);
var
  n, k: integer;
begin
  n := length(id_str);
  f.Write(n, sizeOf(n));
  f.Write(id_str[1], n * sizeOF(id_str[1]));

  n := length(Neuronas);
  f.Write(n, SizeOf(n));
  for k := 0 to high(Neuronas) do
  begin
    f.WriteAnsiString( Neuronas[k].ClassName  );
    Neuronas[k].WriteToBin(f);
  end;
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;
var
  kNeurona: integer;
begin
  for kNeurona := 0 to high(Neuronas) do
    Neuronas[kNeurona].evaluar;
end;

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

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

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

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

function TCapaNeuronasBase.AgregarNeuronaNeutra(
  CapaSiguiente: TCapaNeuronasBase; tipoSaturacion: TTipoSaturacion): TNeurona;
var
  aNeurona, bNeurona: TNeuronaConEntradas;
  k: integer;

begin
  aNeurona:= Neuronas[ high( Neuronas ) ] as TNeuronaConEntradas;

  aNeurona:= TNeuronaConEntradas.Create(
    id_str + INtToStr( length( Neuronas ) ),
    self,
    aNeurona.entradas,
    tipoSaturacion,
    aNeurona.tipoEntrenamiento );

  setlength( Neuronas, length( Neuronas ) + 1 );
  Neuronas[ high( Neuronas ) ]:= aNeurona;

  result:= aNeurona;

  if CapaSiguiente = nil then exit;

  bNeurona:= CapaSiguiente.Neuronas[0] as TNeuronaConEntradas;

  case bNeurona.tipoSaturacion of
    CTS_Lineal, CTS_Tanh:
      begin
        aNeurona.bias:= 0.0;
        vclear( aNeurona.ro );
      end;
    CTS_Sigmoide:
      begin
        aNeurona.bias:= -10;
        vclear( aNeurona.ro );
      end;
   else
   begin
        aNeurona.bias:= 0.0;
        vclear( aNeurona.ro );
   end;
  end;

  // Ahora conectamos las neuronas de la capa siguiente a la salida de la nueva neurona.
  for k:= 0 to high( CapaSiguiente.Neuronas ) do
  begin
    bNeurona:= CapaSiguiente.Neuronas[k] as TNeuronaConEntradas;
    setlength( bNeurona.entradas, length(bNeurona.entradas)+1 );
    bNeurona.entradas[ high( bNeurona.entradas ) ]:= aNeurona;
    setlength( bNeurona.ro, length( bNeurona.ro )+1 );
    setlength( bNeurona.dE_dro_, length( bNeurona.ro ));
    setlength( bNeurona.dE_dro_ant, length( bNeurona.ro ));
    if (bNeurona.tipoSaturacion = CTS_Max) or ( bNeurona.tipoSaturacion = CTS_PonderSoftmax ) then
      setlength( bNeurona.auxSoftMaxProd, length( bNeurona.ro ));
    bNeurona.ro[ high( bNeurona.ro ) ]:= 1.0;
  end;
end;



{ TCerebro }

constructor TCerebro.Create(nEntradas: integer; xCntNeuronasPorCapa: TDAofNInt;
  flg_saturarUltimaCapa: boolean);
begin
  if flg_saturarUltimaCapa then
    Create( nEntradas, xCntNeuronasPorCapa, CTS_Sigmoide, CTS_Sigmoide )
  else
    Create( nEntradas, xCntNeuronasPorCapa, CTS_Sigmoide, CTS_Lineal );
end;

constructor TCerebro.Create(nEntradas: integer; xCntNeuronasPorCapa: TDAofNInt;
  tipoSaturacionSaturacionPenultimaCapa, tipoSaturacionCapaSalida: TTipoSaturacion; tipoSatDefault: TTipoSaturacion = CTS_Tanh;
  flg_AgregarUnaLinealEnTodasLasCapasIntermedias: boolean = false );
var
  kCapa: integer;
  entradas: TDAofNeurona;
  tipoSaturacion: TTipoSaturacion;
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
       tipoSaturacion := tipoSaturacionCapaSalida
    else if kCapa = (High(capas) - 1) then
        tipoSaturacion := tipoSaturacionSaturacionPenultimaCapa
    else
        tipoSaturacion := tipoSatDefault;

    if kCapa < high(capas) then
      capas[kCapa] := TCapaNeuronas.Create('Capa' + IntToStr(kCapa),
        entradas, xCntNeuronasPorCapa[kCapa], tipoSaturacion, 1, flg_AgregarUnaLinealEnTodasLasCapasIntermedias )
    else
      capas[kCapa] := TCapaNeuronas.Create('Capa' + IntToStr(kCapa),
        entradas, xCntNeuronasPorCapa[kCapa], tipoSaturacion, 1, false );

    entradas := capas[kCapa].Neuronas;
  end;
  Salida_CapaN := capas[high(capas)];
end;

constructor TCerebro.Create(nEntradas: integer; xCntNeuronasPorCapa: TDAofNInt;
  tiposSaturacionCapa: TDaOfTipoSaturacion;
  flg_AgregarUnaLinealEnTodasLasCapasIntermedias: boolean);
var
  kCapa: integer;
  entradas: TDAofNeurona;
  tipoSaturacion: TTipoSaturacion;
  NSalidas: integer;

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
    tipoSaturacion := tiposSaturacionCapa[kCapa];
    if kCapa < High( capas ) then
        capas[kCapa] := TCapaNeuronas.Create('Capa' + IntToStr(kCapa),
          entradas, xCntNeuronasPorCapa[kCapa], tipoSaturacion, 1, flg_AgregarUnaLinealEnTodasLasCapasIntermedias )
    else
      capas[kCapa] := TCapaNeuronas.Create('Capa' + IntToStr(kCapa),
        entradas, xCntNeuronasPorCapa[kCapa], tipoSaturacion, 1, false  );

    entradas := capas[kCapa].Neuronas;
  end;
  Salida_CapaN := capas[high(capas)];
  NSalidas:= length( Salida_CapaN.Neuronas );
  setlength( flg_YMin, NSalidas );
  setlength( flg_YMax, NSalidas );
  setlength( YMin, NSalidas );
  setlength( YMax, NSalidas );
end;

constructor TCerebro.CreateFromArchi(archi: string);
var
  f: TFileStream;
  sext: string;
begin
  sext := ExtractFileExt(archi);
  if sext = '.bin' then
  begin
    f := TFileStream.Create(archi, fmOpenRead + fmShareDenyWrite );
    CreateFromBin( f );
    f.Free;
  end
  else if sext = '.xlt' then
  begin
    f := TFileStream.Create(archi, fmOpenRead + fmShareDenyWrite );
    CreateFromXLT(f);
    f.Free;
  end
  else
    raise Exception.Create(' no es posible manejar la extensión: ' + sext);
end;

constructor TCerebro.CreateFromBin(f: TSTream);
var
  nCapas, kCapa: integer;
  entradas: TDAofNeurona;
  clase: string;

begin
  inherited Create;
  clase:= f.ReadAnsiString;
  if clase = TCapaNeuronasEntrada.ClassName then
   Entrada_Capa0 := TCapaNeuronasEntrada.CreateFromBIN(f)
  else
    raise Exception.Create( 'TCerebro.CreateFromBin, se esperaba una capa de entrada. Se leyó: '+clase );

  f.Read(nCapas, sizeOf(nCapas));
  SetLength(capas, nCapas);
  entradas := Entrada_Capa0.Neuronas;
  for kCapa := 0 to high(capas) do
  begin
    clase:= f.ReadAnsiString;
    if clase = TCapaNeuronas.ClassName then
      capas[kCapa] := TCapaNeuronas.CreateFromBIN(f, entradas)
    else if clase = TCapaSoftmax.ClassName then
      capas[kCapa] := TCapaSoftmax.CreateFromBIN( f, entradas )
    else
      raise Exception.Create( 'TCerebro.CreateFromBin. Identificador de clase de Capa desconocido: '+clase );

    entradas := capas[kCapa].Neuronas;
  end;
  Salida_CapaN := capas[high(capas)];
  setlength( flg_Ymin, Nsalidas );
  setlength( flg_YMax, NSalidas );
  setlength( YMin, NSalidas );
  setlength( Ymax, NSalidas );
  f.Read( flg_Ymin[0], NSalidas * SizeOf( flg_YMin[0] ) );
  f.Read( flg_Ymax[0], NSalidas * SizeOf( flg_YMax[0] ) );
  f.Read( Ymin[0], NSalidas * SizeOf( YMin[0] ) );
  f.Read( Ymax[0], NSalidas * SizeOf( YMax[0] ) );

end;

constructor TCerebro.CreateFromXLT(f: TSTream);
var
  nCapas, kCapa: integer;
  entradas: TDAofNeurona;
  s: string;
begin
  inherited Create;
  Entrada_Capa0 := TCapaNeuronasEntrada.CreateFromXLT(f);

  readlnS(f, s);
  nCapas := StrToInt(s);
  SetLength(capas, nCapas);
  entradas := Entrada_Capa0.Neuronas;
  for kCapa := 0 to high(capas) do
  begin
    capas[kCapa] := TCapaNeuronas.CreateFromXLT(f, entradas);
    entradas := capas[kCapa].Neuronas;
  end;
  Salida_CapaN := capas[high(capas)];

  readlnS( f, s );
  flg_YMin:= StrToDAOfBoolean( s, #9 );

  readlnS( f, s );
  flg_YMax:= StrToDAOfBoolean( s, #9 );

  readlnS( f, s );
  YMin:= StrToDAOfNReal_( s, #9 );

  readlnS( f, s );
  YMax:= StrToDAOfNReal_( s, #9 );

end;

procedure TCerebro.WriteToBin(f: TStream);
var
  nCapas, kCapa: integer;
begin
  Entrada_Capa0.WriteToBIN(f);
  nCapas := length(capas);
  f.Write(nCapas, sizeOf(nCapas));
  for kCapa := 0 to high(capas) do
    capas[kCapa].WriteToXLT(f);

  f.Write( flg_Ymin[0], NSalidas * SizeOf( flg_YMin[0] ) );
  f.Write( flg_Ymax[0], NSalidas * SizeOf( flg_YMax[0] ) );
  f.Write( Ymin[0], NSalidas * SizeOf( YMin[0] ) );
  f.Write( Ymax[0], NSalidas * SizeOf( YMax[0] ) );

end;

procedure TCerebro.WriteToXLT(f: TStream);
var
  nCapas, kCapa: integer;
  r:  String;
begin
  Entrada_Capa0.WriteToXLT(f);

  nCapas := length(capas);
  writelnS(f, IntToStr(nCapas));
  for kCapa := 0 to high(capas) do
    capas[kCapa].WriteToXLT(f);

  r:= DAOfBooleanToStr( flg_Ymin, #9 );
  writelnS( f, r );
  r:= DAOfBooleanToStr( flg_Ymax, #9 );
  writelnS( f, r );
  r:= DAOfNRealToStr_( Ymin, #9 );
  writelnS( f, r );
  r:= DAOfNRealToStr_( Ymax, #9 );
  writelnS( f, r );

end;

procedure TCerebro.WriteToArchi(archi: string);
var
  f: TFileStream;
  sext: string;
begin
  sext := ExtractFileExt(archi);
  if sext = '.bin' then
  begin
    f := TFileStream.Create(archi, fmCreate + fmShareExclusive);
    WriteToBin( f );
    f.Free;
  end
  else if sext = '.xlt' then
  begin
    f := TFileStream.Create(archi, fmCreate + fmShareExclusive);
    WriteToXLT(f);
    f.Free;
  end
  else
    raise Exception.Create(' no es posible manejar la extensión: ' + sext);
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;

function TCerebro.Clonar: TCerebro;
var
  kCapa: Integer;
  arrCntNeuronasPOrCapa: TDAofNInt;
  tipoSaturacion: array of TTipoSaturacion;
  f:TFileStream;
  archi: String;
begin

  archi:='C:\basura\basura.xlt';


  self.WriteToArchi(archi);


  if not FileExists( archi ) then
    Exit;

  f:= TFileStream.Create( archi, fmOpenRead );

  Result:=TCerebro.CreateFromXLT(f);

  f.Free;


 { SetLength(arrCntNeuronasPOrCapa,Length(capas));
  SetLength(tipoSaturacion,Length(capas));

  for kCapa := 0 to high(capas) do
  begin
    arrCntNeuronasPOrCapa[kCapa]:= capas[kCapa].NNeuronas;
    tipoSaturacion[kCapa]:=capas[kCapa].tipodesaturacion;
  end;

  Result:= TCerebro.Create(Entrada_Capa0.NNeuronas, arrCntNeuronasPOrCapa,
         tipoSaturacion); }

end;

procedure TCerebro.Sort;
var
  kCapa: integer;
begin
  for kCapa := 0 to high(capas)-1 do
    capas[kCapa].Sort( capas[kCapa+1] );
  capas[high(capas)].Sort( nil );
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.SetEntradas_prnd(X: TVectR; prnd: NReal);
var
  jEntrada: integer;
  a: NReal;
  aNeurona: TNeuronaEntrada;
begin
  for jEntrada := 0 to high(Entrada_Capa0.Neuronas) do
  begin
    aNeurona:= CapaEntrada.Neuronas[jEntrada] as TNeuronaEntrada;
    a:= X.e(jEntrada + 1) + prnd * (2.0 * ( random - 0.5 ) ) / aNeurona.ro;
    SetEntrada(jEntrada, a );
  end;
end;

procedure TCerebro.evaluar;
var
  kCapa: integer;
begin

  Entrada_Capa0.evaluar;

  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;
var
  v: NReal;
begin
  v:=  CapaSalida.Neuronas[jSalida].salida;
  if flg_YMin[jSalida] then
    v:= max( YMin[jSalida], v );
  if flg_YMax[jSalida] then
    v:= min( YMax[jSalida], v );
  Result := v;
end;


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

procedure TCerebro.SetMaxSalida(jSalida: integer; v_Max: NReal);
begin
  flg_YMax[jSalida]:= true;
  YMax[jSalida]:= v_Max;
end;

procedure TCerebro.SetMinSalida(jSalida: integer; v_Min: NReal);
begin
  flg_YMin[jSalida]:= true;
  YMin[jSalida]:= v_Min;
end;

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

  setlength( flg_YMin, 0 );
  setlength( flg_YMax, 0 );
  setlength( YMin, 0 );
  setlength( YMax, 0 );
  inherited destroy;
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
  BackPropagation_MuestrasAcumuladas := 0;
  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;
  Inc(BackPropagation_MuestrasAcumuladas);
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;

  Inc(BackPropagation_MuestrasAcumuladas);
end;

procedure TCerebro.BackPropagation_addMuestra(derr: NReal);
var
  kNeurona: integer;
  aNeurona: TNeurona;
  kCapa: integer;
begin
  // Cargamos los errores de la última capa
 aNeurona := Salida_CapaN.Neuronas[0];
    if aNeurona is TNeuronaConEntradas then
      (aNeurona as TNeuronaConEntradas).dErr := derr;

 for kCapa := High(Capas) downto 0 do
    Capas[kCapa].BackPropagation_AddMuestra;

  Inc(BackPropagation_MuestrasAcumuladas);
end;

procedure TCerebro.CalcGradX(kSalida: integer);
var
  kNeurona: integer;
  kCapa: integer;
begin
  // limpiamos las variable dErr de toda la red.
  BackPropagation_ClearErr;

  // Cargamos errores ficticios en la última capa
  // con un 1.0 en la salida que nos interesa derivar y 0.0 en las demás
  for kNeurona := 0 to high(Salida_CapaN.Neuronas) do
  begin
    if kNeurona = kSalida then
      Salida_CapaN.Neuronas[kNeurona].dErr := 1.0
    else
      Salida_CapaN.Neuronas[kNeurona].dErr := 0.0;
  end;

  // Ahora propagamos los errores para tener las derivadas
  for kCapa := High(Capas) downto 0 do
    Capas[kCapa].BackPropagation_gradX;

  // Ahora sobre la capa de entrada, los dErr tienen las derivadas
  // y pueden leerse con GetGradX
end;

procedure TCerebro.GetGradX(gradX: TDAOfNReal);
var
  kNeurona: integer;
  aNeurona: TNeuronaEntrada;
begin
  for kNeurona := 0 to high(Entrada_Capa0.Neuronas) do
  begin
    aNeurona := Entrada_Capa0.Neuronas[kNeurona] as TNeuronaEntrada;
    gradX[kNeurona] := aNeurona.dErr * aNeurona.ro;
  end;
end;

procedure TCerebro.GetGradX(gradX: TVectR);
var
  kNeurona: integer;
  aNeurona: TNeuronaEntrada;
begin
  for kNeurona := 0 to high(Entrada_Capa0.Neuronas) do
  begin
    aNeurona := Entrada_Capa0.Neuronas[kNeurona] as TNeuronaEntrada;
    gradX.Pon_E(kNeurona + 1, aNeurona.dErr * aNeurona.ro);
  end;
end;


procedure TCerebro.BackPropagation_DarPaso(paso, beta_momento, gamma_momento, alfa_regularizacion: NReal);
var
  kCapa: integer;
begin
  for kCapa := 0 to high(capas) do
    Capas[kCapa].BackPropagation_DarPaso(BackPropagation_MuestrasAcumuladas,
      paso, beta_momento, gamma_momento, alfa_regularizacion);
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.print_debug(const archi: string);
var
  f: textfile;
begin
  assignfile(f, archi);
  rewrite(f);
  print_debug(f);
  closefile(f);

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;
  ySal, yMuestra: NReal;
begin
  res := 0;
  for k := 1 to NSalidas do
  begin
    ySal := GetSalida( k - 1 );
    yMuestra := y.e(k);
    a := ySal - yMuestra;
    e.pon_e(k, a);
    res := res + sqr(a);
  end;
  Result := res;
end;

function TCerebro.calc_err2(var e: NReal;  y: NReal): NReal;
begin
  e := GetSalida(0) - y;
  Result := sqr(e);
end;

procedure TCerebro.EscalarEntradas(aData: TDAOfRecXCF);
var
  xmin, xmax: TVectR;
  jEntrada: integer;
  aNeurona: TNeuronaEntrada;
  dx: NReal;
  xmed: NReal;
begin
  CalcMinMax_Entradas_conjuntoXCF(xmin, xmax, aData);
  for jEntrada := 1 to xmin.n do
  begin
    aNeurona := Entrada_Capa0.Neuronas[jEntrada - 1] as TNeuronaEntrada;
    xMed := (xmin.e(jEntrada) + xmax.e(jEntrada)) / 2.0;
    dx := (xmax.e(jEntrada) - xmin.e(jEntrada)) / 2.0;
    if dx > 1E-20 then
    begin
      aNeurona.ro := 1.0 / dx;
    end
    else
    begin
      dx := 1.0;
      aNeurona.ro := 1.0;  // Caso de entrada fija. No importa lo que pongamos.
    end;
    aNeurona.bias := -xMed / dx;
  end;
  xmin.Free;
  xmax.Free;
end;

procedure TCerebro.EscalarCapaSalida(aData: TDAOfRecXCF);
var
  ymin, ymax: TVectR;
  jEntrada, jSalida: integer;
  aNeurona: TNeuronaConEntradas;
  dy: NReal;
  ymed: NReal;
begin
  CalcMinMax_Salidas_conjuntoXCF(ymin, ymax, aData);

  for jSalida := 1 to ymin.n do
  begin
    aNeurona := Salida_CapaN.Neuronas[jSalida - 1] as TNeuronaConEntradas;
    yMed := (ymin.e(jSalida) + ymax.e(jSalida)) / 2.0;
    dy := (ymax.e(jSalida) - ymin.e(jSalida)) / 2.0;
    for jEntrada := 0 to high(aNeurona.ro) do
      aNeurona.ro[jEntrada] := dy + jEntrada / length( aNeurona.ro ) /10.0;
    aNeurona.bias := yMed;
  end;

  ymin.Free;
  ymax.Free;
end;

procedure TCerebro.IntercambiarNeuronas(kCapa, hNeurona, jNeurona: integer);
var
  NeuronaAux: TNeurona;

begin
  if kCapa < high( capas ) then
     capas[kCapa+1].IntercambiarReferenciasANeuronas( hNeurona, jNeurona );

  NeuronaAux:= capas[kCapa].Neuronas[ hNeurona ];
  capas[kCapa].Neuronas[ hNeurona ]:= capas[kCapa].Neuronas[ jNeurona ];
  capas[kCapa].Neuronas[ jNeurona ]:= NeuronaAux;
end;


{ TCapaNeuronas }

constructor TCapaNeuronas.Create(xid_str: string;
  const xEntradas: TDAofNeurona; nNeuronas: integer;
  tipoSaturacion: TTipoSaturacion; tipoEntrenamiento: integer;
  flg_AgregarUnaLinealAdicional: boolean);
var
  kNeurona: integer;
begin
  entradas:= xEntradas;
  if flg_AgregarUnaLinealAdicional then
  begin
    inherited Create(xid_str, nNeuronas+1);
    for kNeurona := 0 to high(Neuronas)-1 do
      Neuronas[kNeurona] := TNeuronaConEntradas.Create(
        xid_str + '_N' + IntToStr(kNeurona),
        self,
        xentradas, tipoSaturacion,
        tipoEntrenamiento);

    Neuronas[high(Neuronas)] := TNeuronaConEntradas.Create(
      xid_str + '_N' + IntToStr(kNeurona), self, xentradas, CTS_Lineal,
      tipoEntrenamiento);
  end
  else
  begin
    inherited Create(xid_str, nNeuronas);
    for kNeurona := 0 to high(Neuronas) do
      Neuronas[kNeurona] := TNeuronaConEntradas.Create(
        xid_str + '_N' + IntToStr(kNeurona),
        self, xentradas, tipoSaturacion,
        tipoEntrenamiento);
  end;
end;


constructor TCapaNeuronas.CreateFromXLT(f: TStream; xEntradas: TDAofNeurona);
var
  k: integer;
  Clase: string;
begin
  inherited CreateFromXLT(f);
  entradas:= xEntradas;

  for k := 0 to high(Neuronas) do
  begin
    readlnS( f, clase );
    if clase = TNeuronaConEntradas.ClassName then
       Neuronas[k]:= TNeuronaConEntradas.CreateFromXLT( f, xEntradas, self  )
    else
       raise Exception.Create( 'Error, clase de neurona desconocida: '+ clase );
  end;

end;

constructor TCapaNeuronas.CreateFromBIN(f: TStream; xEntradas: TDAofNeurona);
var
  k: integer;
  clase: string;

begin
  inherited CreateFromBIN(f);
  entradas:= xEntradas;
  for k := 0 to high(Neuronas) do
  begin
    clase:= f.ReadAnsiString;
    if clase = TNeuronaConEntradas.ClassName then
       Neuronas[k]:= TNeuronaConEntradas.CreateFromBin( f, xEntradas, self  )
    else
       raise Exception.Create( 'Error, clase de neurona desconocida: '+ clase );
  end;

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(capaSiguiente: TCapaNeuronas);
var
  kNeurona, kNeuronaAux, kNeuronaAMover: integer;
  neuronasAux: TDAofNeurona;
  id_strAuxs: array of string;
begin
  if capaSiguiente <> nil then
    QuickSort_Creciente( TDAOfPtr( Neuronas ), @CompareNeuronas, capaSiguiente.IntercambiarReferenciasANeuronas )
  else
    QuickSort_Creciente( TDAOfPtr( Neuronas ), CompareNeuronas, nil );
end;


destructor TCapaNeuronas.destroy;
var
  kNeurona: integer;
begin
  for kNeurona := 0 to high(Neuronas) do
    Neuronas[kNeurona].Free;
  setlength(Neuronas, 0);
  inherited destroy;
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_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_gradX;
var
  kNeurona: integer;
  aNeurona: TNeuronaConEntradas;
begin
  for kNeurona := 0 to high(neuronas) do
  begin
    aNeurona := Neuronas[kNeurona] as TNeuronaConEntradas;
    aNeurona.BackPropagation_gradX;
  end;
end;




procedure TCapaNeuronas.BackPropagation_DarPaso(NMuestras: integer;
  paso, beta_momento, gamma_momento, alfa_Regularizacion: NReal);
var
  kNeurona: integer;
  aNeurona: TNeuronaConEntradas;
begin
  for kNeurona := 0 to high(neuronas) do
  begin
    aNeurona := Neuronas[kNeurona] as TNeuronaConEntradas;
    aNeurona.BackPropagation_DarPaso(NMuestras, paso, beta_momento, gamma_momento, alfa_Regularizacion );
  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;

function TCapaNeuronas.IntercambiarReferenciasANeuronas(k, j: integer): boolean;
var
  h: integer;
  aNeurona: TNeuronaConEntradas;
begin
   for h:= 0 to high( neuronas ) do
   begin
     aNeurona:= neuronas[h] as TNeuronaConEntradas;
     aNeurona.intercambiarReferenciasANeuronas( k, j );
   end;
   result:= true;
end;

constructor TNeuronaConEntradas.Create(const xid_str: string;
  capa: TCapaNeuronasBase;
  const xEntradas: TDAOfNeurona; xTipoSaturacion: TTipoSaturacion;
  tipoEntrenamiento: integer);
begin
  inherited Create(xid_str, capa );
  entradas := xEntradas;
  tipoSaturacion := xTipoSaturacion;
  bias:= 0;
  setlength(ro, length(entradas));
  setlength(dE_dro_, length(entradas));
  vclear(dE_dro_);
  setlength(dE_dro_ant, length(entradas));
  vclear(dE_dro_ant);
  self.tipoEntrenamiento := tipoEntrenamiento;

  if (xTipoSaturacion = CTS_PonderSoftmax) or (xTipoSaturacion = CTS_Max) then
  begin
    setlength(auxSoftMaxProd, length(xEntradas));
    tipoEntrenamiento := 0;
  end;

end;

procedure TNeuronaConEntradas.evaluar;
var
  kEntrada: integer;
  a: NReal;
begin
  a := bias;

  if (TipoSaturacion = CTS_PonderSoftmax) or (TipoSaturacion = CTS_Max) then
  begin
    for kEntrada := 0 to high(ro) do
      auxSoftMaxProd[kEntrada] := Entradas[kEntrada].salida;
  end
  else
  begin
    for kEntrada := 0 to high(ro) do
      a := a + ro[kEntrada] * Entradas[kEntrada].salida;
  end;

  case tipoSaturacion of
    CTS_Lineal: ; // nada, no satura
    CTS_Tanh: a := tanh(a);
    CTS_Sigmoide: a := sigmoide(a);
    CTS_Logica: if a > 0 then
        a := 1
      else
        a := 0;
    CTS_Max: a := vmax(kOfMax, auxSoftMaxProd);
    CTS_ReLu: if a < 0 then
        a := 0;
    CTS_PonderSoftmax: a := PonderSoftMax(auxSoftMaxProd, auxSoftMaxProd);
    else
      raise Exception.Create('tipoSaturación - no implementado');
  end;
  salida := a;

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

end;

constructor TNeuronaConEntradas.CreateFromBIN(f: TStream; xEntradas: TDAofNeurona; capa: TCapaNeuronasBase);
var
  n, k: integer;
  s: string;
begin
  inherited CreateFromBIN( f, capa );

  entradas := xEntradas;
  f.Read(bias, sizeOf(bias));
  f.Read(tipoSaturacion, SizeOf(tipoSaturacion));
  f.Read(tipoEntrenamiento, SizeOf(tipoEntrenamiento));

  f.Read(n, sizeof(n));
  setlength(ro, n);
  f.Read(ro[0], n * SizeOf(ro[0]));

  setlength(dE_dro_, length(entradas));
  vclear(dE_dro_);
  setlength(dE_dro_ant, length(entradas));
  vclear(dE_dro_ant);
  dE_dbias_ := 0;
  dE_dbias_ant := 0;
  derr := 0;
end;

constructor TNeuronaConEntradas.CreateFromXLT(f: TStream; xEntradas: TDAofNeurona; capa: TCapaNeuronasBase);
var
  n, k: integer;
  s: string;
begin
  inherited CreateFromXLT(f, capa);

  entradas := xEntradas;
  readlnS(f, s);

  bias := NextFloat(s, #9);

  readlnS(f, s);
  k := NextInt(s, #9);
  tipoSaturacion := TTipoSaturacion(k);

  readlnS(f, s);
  k := NextInt(s, #9);
  tipoEntrenamiento := k;

  if (tipoSaturacion = CTS_PonderSoftmax) or (tipoSaturacion = CTS_Max) then
  begin
    setlength(auxSoftMaxProd, length(xEntradas));
    tipoEntrenamiento := 0;
  end;

  readlnS(f, s);
  n := nextInt(s, #9);
  setlength(ro, n);
  for k := 0 to n - 1 do
    ro[k] := NextFloat(s, #9);

  setlength(dE_dro_, length(entradas));
  vclear(dE_dro_);
  setlength(dE_dro_ant, length(entradas));
  vclear(dE_dro_ant);
  dE_dbias_ := 0;
  dE_dbias_ant := 0;
  derr := 0;
end;


function TNeuronaConEntradas.DimParametros: integer;
begin
  if tipoEntrenamiento > 0 then
    Result := 1 + length(ro)
  else
    Result := 0;
end;

procedure TNeuronaConEntradas.GetParametros(var parametros: TVectR;
  var kOffset: integer);
var
  k: integer;
begin
  if tipoEntrenamiento = 0 then
    exit;
  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
  if tipoEntrenamiento = 0 then
    exit;
  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
  if tipoEntrenamiento = 0 then
    exit;
  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.WriteToXLT(f: TStream);
var
  n, k: integer;
begin
  inherited WriteToXLT(f);
  writelnS(f, FloatToStr(bias) + #9 + 'Bias');

  writelnS(f, IntToStr(Ord(tipoSaturacion)) + #9 + 'Tipo saturación');
  writelnS(f, IntToStr(tipoEntrenamiento) + #9 + 'Tipo entrenamiento');

  n := length(ro);
  WriteS(f, IntToStr(n));
  for k := 0 to n - 1 do
    WriteS(f, #9 + FloatToStr(ro[k]));
  WritelnS(f, '');
end;


procedure TNeuronaConEntradas.WriteToBin(f: TStream);
var
  n: integer;
begin
  inherited WriteToBIN(f);
  f.Write(bias, sizeof(bias));
  f.Write(tipoSaturacion, sizeof(tipoSaturacion));
  f.Write(tipoEntrenamiento, SizeOf(tipoEntrenamiento));
  n := length(ro);
  f.Write(n, sizeof(n));
  f.Write(ro[0], length(ro) * sizeof(ro[0]));
end;



procedure TNeuronaConEntradas.WriteToConsola;
var
  n, k: integer;
begin
  inherited WriteToConsola;
  writeln('Bias: ', bias: 0: 4, ', tipoSat: ', Ord(tipoSaturacion),
    ', tipoEnt: ', tipoEntrenamiento);
  n := length(ro);
  Write(n);
  for k := 0 to n - 1 do
    Write(#9, ro[k]: 0: 4);
  writeln();
end;

destructor TNeuronaConEntradas.destroy;
begin
  setlength(ro, 0);
  setlength(dE_dro_, 0);
  setlength(dE_dro_ant, 0);
  setlength(auxSoftMaxProd, 0);

  inherited destroy;
end;

procedure TNeuronaConEntradas.RandomInit(paso: NReal);
var
  k: integer;
begin
  if tipoEntrenamiento = 0 then
    exit;
  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;
  dE_dbias_ant := 0;
  vclear(dE_dro_);
  vclear(dE_dro_ant);
  dErr := 0;
end;


procedure TNeuronaConEntradas.BackPropagation_addMuestra;
var
  aNeurona: TNeurona;
  k: integer;

begin
  BackPropagation_TrauducirErrorALaEntrada;

  // 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;

  for k := 0 to high(Entradas) do
  begin
    aNeurona := Entradas[k];
    aNeurona.dErr := aNeurona.dErr + dErr * ro[k];
  end;

end;


procedure TNeuronaConEntradas.BackPropagation_gradX;
var
  aNeurona: TNeurona;
  k: integer;

begin
  BackPropagation_TrauducirErrorALaEntrada;

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

procedure TNeuronaConEntradas.BackPropagation_TrauducirErrorALaEntrada;
var
  dev: NReal;
  k, j: integer;

begin
  case tipoSaturacion of
    CTS_Lineal: dev := 1;
    CTS_Tanh:
    begin
     {$IFDEF SIGMOIDEDEVMIN}// intentando prevenir que se queden clavadas
      dev := max(1 - sqr(salida), 2E-20);
     {$ELSE}
      dev := 1 - sqr(salida);
     {$ENDIF}
      // Pasamos el error de la Salida de a la entrada
      derr := dev * derr;
    end;
    CTS_Sigmoide:
    begin
      {$IFDEF SIGMOIDEDEVMIN}// intentando prevenir que se queden clavadas
      dev := max(salida * (1 - salida), 2E-20);
      {$ELSE}
      dev := salida * (1 - salida);
      {$ENDIF}
      // Pasamos el error de la Salida de a la entrada
      derr := dev * derr;
    end;
    CTS_Logica:    // OJO REVISAR
    begin
      if derr > 0 then  // Salida > dato
        if salida > 0.5 then
          dev := 1.0  // puedo bajar la salida
        else
          dev := 0.0 // no puedo bajar más la salida
      else// Salida < dato
      if salida < 0.5 then
        dev := 1.0 // puedo subir la salida
      else
        dev := 0; // no puedo subir la salida
      derr := dev * derr;
    end;
    CTS_ReLu:   // Ojo revisar
    begin
      if salida > 0 then
        dev := 1.0
      else
        dev := 1.0E-10;
      derr := dev * derr;
    end;
    CTS_Max:
    begin
      // vclear( ro );
      for k:= 0 to high( ro ) do
        ro[k]:= 1.0E-5; // le dejo ver un poquito
      ro[kOfMax] := 1.0;
    end;
    CTS_PonderSoftmax:
    begin
      // dro_i / dx_h = ( ro_i^2 ) ( i = h ) - ro_i * r_h
      // y = sum ro_i x_i
      // dy/dx_h = sum dro_i/dx_h x_i + sum ro_i dx_i/dxh
      // dy/dx_h =  sum ( ( ro_i^2 ) ( i = h )- sum ro_i * ro_h ) x_i + ro_h
      // dy/dx_h = ro_h^2 x_h - sum ro_i * ro_h * x_i + ro_h
      for k := 0 to high(ro) do
      begin
        ro[k] := sqr(auxSoftMaxProd[k]) * entradas[k].salida + auxSoftMaxProd[k];
        for j := 0 to high(auxSoftMaxProd) do
          ro[k] := ro[k] - auxSoftMaxProd[k] * auxSoftMaxProd[j] * entradas[j].salida;
      end;
    end;
    CTS_Softmax:
    begin
      // Suponemos que en cada ro

    end;
  end;
end;



procedure TNeuronaConEntradas.BackPropagation_DarPaso(NMuestras: integer;
  paso, beta_momento, gamma_momento, alfa_regularizacion: NReal);
var
  k: integer;
begin
  if tipoEntrenamiento = 0 then
    exit;

  for k := 0 to high(ro) do
    dE_dro_ant[k] := dE_dro_ant[k] * beta_momento + dE_dro_[k] / NMuestras *
      gamma_momento ;
  dE_dbias_ant := dE_dbias_ant * beta_momento + dE_dbias_ / NMuestras * gamma_momento;

  for k := 0 to high(ro) do
    ro[k] := ro[k] - paso * ( dE_dro_ant[k] + alfa_Regularizacion * ro[k] );
//  bias := bias - paso * ( dE_dbias_ant + alfa_Regularizacion * bias );
  bias := bias - paso * dE_dbias_ant; // En los bias no aplico regularización
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, 'dE_dro_ant:');
  for k := 0 to high(ro) do
    Write(dbgfile, #9, dE_dro_ant[k]);
  writeln(dbgfile, #9, 'dE_dbias_ant:', #9, dE_dbias_ant);

  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.IntercambiarReferenciasANeuronas(k, j: integer);
var
  Neurona_aux: TNeurona;
begin
  Neurona_aux:= entradas[k];
  entradas[k]:= entradas[j];
  entradas[j]:= Neurona_aux;
  vswap( ro[k], ro[j] );
end;

function TNeuronaConEntradas.GetFormula: string;
var
  salida: string;
  entrada: string;
  resa, resb: string;
  k: integer;

function calc_suma_producto_entradas: string;
var
  k: integer;
  res: string;
  aNeurona: TNeurona;
begin
  res:= FloatToStr( bias );
  for k:= 0 to high( entradas ) do
  begin
    aNeurona:= self.entradas[k];
    res:= FloatToStr( ro[k] ) +' * '+aNeurona.id_str+'_y';
  end;
  result:= res;
end;

begin
  salida:= self.id_str+'_y';
  case self.tipoSaturacion of
    CTS_Lineal: resb:= ';';
    CTS_Tanh:
      begin
        resa:= 'tanh( ';
        entrada:= calc_suma_producto_entradas;
        resb:= ' );';
      end;
    CTS_Sigmoide:
      begin
        resa:= 'sigmoide( ';
        entrada:= calc_suma_producto_entradas;
        resb:= ' );';
      end;
    CTS_Logica:
      begin  // x > 0
        resa:= 'si( ';
        entrada:= calc_suma_producto_entradas;
        resb:= ', 1.0 ,0.0 );';
      end;
    CTS_ReLu: // ( x > 0 ) x
    begin  // x > 0
      resa:= 'si( ';
      entrada:= calc_suma_producto_entradas;
      resb:= ', 1.0 ,0.0 );';
    end;
    CTS_Max: // = max(ro_i x_i )
    begin
      resa:= '';
      resb:= ';';
      for k:= 0 to high( entradas )-1 do
      begin
         resa:= resa+ 'max( '+FloatToStr( ro[k] )+' * '+Entradas[k].id_str+'_y, ';
         resb:= ')'+resb;
      end;
      k:= high( entradas );
      resa:= resa+ FloatToStr( ro[k] )+' * '+Entradas[k].id_Str+'_y ';
    end;
    (**
    ????
    CTS_PonderSoftmax // = sum w_i x_i ; donde w_i = exp( ro_i x_i ) / sum ( exp( ro_i x_i ) )
       **)
  end;

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 ... ', 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;

  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, momento, alfa_Regularizacion: NReal): NReal;
var
  kMuestra: integer;
  xRec: TRecXCF;
  e: TVectR;
  e2: NReal;
  beta_momento, gamma_momento: NReal;
  NMuestras: integer;
begin
  beta_momento := momento;
  gamma_momento := 1 - beta_momento;

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

(*
  Calcula el gradiente por BackPropagation y por incrementos
  direccionales solo para verificar la implementación.
*)
procedure TestGrad_BP(var aCerebro: TCerebro; aData: TDAOfRecXCF; kMuestra: integer);
var
  xRec: TRecXCF;
  e: TVectR;
  e2, e2base: NReal;

  pbase, pinc: TVectR;
  gradBP, gradDP: TVectR;

  Ybase, YInc: TVectR;
  j: integer;

  sal: textfile;

begin
  e := TVectR.Create_Init(aCerebro.NSalidas);
  e2 := 0;

  xRec := aData[kMuestra];
  aCerebro.BackPropagation_init;
  aCerebro.SetEntradas(xRec.X);
  aCerebro.evaluar;

  e2 := e2 + aCerebro.calc_err2(e, xRec.Y);
  aCerebro.BackPropagation_addMuestra(e);

  gradBP := TVectR.Create_init(aCerebro.DimParametros);
  aCerebro.AcumGradParametros(gradBP);

  assignfile(sal, 'cerebro_print_debug.xlt');
  rewrite(sal);
  Write(sal, 'Y: ');
  xRec.Y.WriteXLTSimple(sal);
  writeln(sal, 'CEREBRO ===============');
  aCerebro.print_debug(sal);

  gradDP := TVectR.Create_init(aCerebro.DimParametros);
  YBase := TVectR.Create_Init(aCerebro.NSalidas);
  YInc := TVectR.Create_init(aCerebro.NSalidas);

  pbase := TVectR.Create_Init(aCerebro.DimParametros);
  pinc := TVectR.Create_Init(aCerebro.DimParametros);
  aCerebro.GetParametros(pbase);
  aCerebro.SetEntradas(xRec.X);
  aCerebro.evaluar;
  aCerebro.GetSalidas(YBase);
  e2base := aCerebro.calc_err2(e, xRec.Y);

  writeln(sal, 'GRADIENTES=============', #9, 'gadBP', #9, 'gadEstimadoDelta');
  for j := 1 to pbase.n do
  begin
    pinc.Igual(pbase);
    pinc.acum_e(j, 1.0e-2);
    aCerebro.SetParametros(pinc);
    aCerebro.evaluar;
    e2 := aCerebro.calc_err2(e, xRec.Y);
    gradDP.pon_e(j, (e2 - e2base) / 1.0e-2);
    writeln(sal, j, #9, gradBP.e(j), #9, gradDP.e(j));
  end;

  closefile(sal);

  e.Free;
end;

(*
function EntrenarCerebro_EBP_minibatch(
  var grad: TVectR;
  var aCerebro: TCerebro;
  aData: TDAOfRecXCF;
  paso, momento: NReal;
  NMuestras: integer;
  semilla: integer): NReal;
var
  kMuestra: integer;
  xRec: TRecXCF;
  e: TVectR;
  e2: NReal;
  k: Integer;
  parametros, gradx: TVectR;

begin
  parametros:= TVectR.Create_Init( aCerebro.DimParametros );
  aCerebro.GetParametros( parametros );
  gradx:= TVectR.Create_Init( aCerebro.DimParametros );
  gradx.Ceros;
  RandSeed:= semilla;
  e := TVectR.Create_Init(aCerebro.NSalidas);
  e2 := 0;
  for k := 1 to NMuestras do
  begin
    kMuestra:= random( length( aData ) );
    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.AcumGradParametros( gradx );
  end;
  grad.PorReal( momento );
  grad.sumRPV( 1.0 - momento , gradx );
  Parametros.sumRPV( -paso, grad );
  aCerebro.SetParametros( parametros );
  Result := e2 / length(aData);
  parametros.Free;
  gradx.Free;
  e.Free;
end;

*)

function EntrenarCerebro_EBP_minibatch(
  // var grad: TVectR;
  var aCerebro: TCerebro; aData: TDAOfRecXCF; paso, momento, alfa_Regularizacion: NReal;
  NMuestras: integer; semilla: integer; prnd: NReal;
  jSalidaPesosPorRango: integer; pesosPorRango: TDAofNReal; ymin: NReal;
  dy: NReal): NReal;
var
  kMuestra: integer;
  xRec: TRecXCF;
  e: TVectR;
  e2, d_e2: NReal;
  k: integer;
  flg_todas: boolean;
  flg_InyectarRuido: boolean;
  kRango: integer;

  function rnd(k: integer): integer;
  begin
    if flg_todas then
      Result := k
    else
      Result := random(length(aData));
  end;

begin
  RandSeed := semilla;
  e := TVectR.Create_Init(aCerebro.NSalidas);
  e2 := 0;

  flg_InyectarRuido := prnd <> 0;


  if NMuestras <= 0 then
  begin
    NMuestras := length(aData);
    flg_todas := True;
  end
  else
    flg_todas := False;

  aCerebro.BackPropagation_init;
  for k := 1 to NMuestras do
  begin
    aCerebro.BackPropagation_ClearErr;
    kMuestra := rnd(k - 1);
    xRec := aData[kMuestra];

    if flg_InyectarRuido then
     aCerebro.SetEntradas_prnd(xRec.X, prnd )
    else
     aCerebro.SetEntradas(xRec.X);

    aCerebro.evaluar;
    d_e2 := aCerebro.calc_err2(e, xRec.Y);

    if pesosPorRango <> nil then
    begin
      kRango:= min( trunc((xRec.Y.e( jSalidaPesosPorRango ) - ymin)/dy ), high( pesosPorRango ) );
      e.PorReal( pesosPorRango[kRango] );
 //     d_e2:= d_e2 * pesosPorRango[kRango];
    end;
    aCerebro.BackPropagation_addMuestra( e );
    e2:= e2 + d_e2;
  end;

  aCerebro.BackPropagation_DarPaso(paso, momento, 1 - momento, alfa_Regularizacion );

  Result := e2 / NMuestras;
  e.Free;
end;


procedure CalcularYGuardar(archi: string; const aCerebro: TCerebro; aData: TDAOfRecXCF);
var
  j: integer;
  kMuestra: integer;
  xRec: TRecXCF;
  e: TVectR;
  e2: NReal;
  sal: textfile;
  parametros: TVectR;
  grad: TVectR;

begin
  assignfile(sal, archi);
  rewrite(sal);

  xRec := aData[0];

  parametros := TVectR.Create_Init(aCerebro.DimParametros);
  grad := TVectR.Create_Init(aCerebro.DimParametros);
  aCerebro.GetParametros(parametros);
  writeln(sal, 'Parámetros: ', #9, parametros.serialize_data(#9));


  for j := 1 to xRec.X.n do
    Write(sal, #9, 'x', j);

  for j := 1 to xRec.Y.n do
    Write(sal, #9, 'y', j);

  for j := 1 to xRec.Y.n do
    Write(sal, #9, 'Y', j);

  for j := 1 to xRec.X.n do
    Write(sal, #9, 'g', j);


  writeln( sal);

  e := TVectR.Create_Init(aCerebro.NSalidas);
  e2 := 0;
  for kMuestra := 0 to high(aData) do
  begin
    xRec := aData[kMuestra];
    Write(sal, #9, xRec.X.serialize_data(#9));
    Write(sal, #9, xRec.Y.serialize_data(#9));

    aCerebro.SetEntradas(xRec.X);
    aCerebro.BackPropagation_init;
    aCerebro.evaluar;

    for j := 1 to xRec.Y.n do
      Write(sal, #9, aCerebro.GetSalida(j - 1));

    e2 := e2 + aCerebro.calc_err2(e, xRec.Y);
    grad.Ceros;
    aCerebro.BackPropagation_addMuestra(e);
    aCerebro.AcumGradParametros(grad);

    Write(sal, #9, grad.serialize_data(#9));

    writeln(sal);
  end;
  e.Free;
  parametros.Free;
  grad.Free;
  closefile(sal);
end;

function StrToTipoSaturacion( TipoSat_str_: string): TTipoSaturacion;
var
  res: TTipoSaturacion;
  Li:string;
  n:Integer;
  TipoSat_str: string;
begin

  res:= CTS_Tanh;
  { CTS_Lineal,
    CTS_Tanh,
    CTS_Sigmoide,
    CTS_Logica, // x > 0
    CTS_ReLu, // ( x > 0 ) x
    CTS_Max, // = max(ro_i x_i )
    CTS_PonderSoftmax
       }

  tipoSat_str := LowerCase( TipoSat_str_ );
  if   TipoSat_str = 'li' then res:=CTS_Lineal
  else if TipoSat_str = 'th' then res:=CTS_Tanh
  else if TipoSat_str = 'sig' then res:=CTS_Sigmoide
  else if TipoSat_str = 'lo' then res:=CTS_Logica
  else if TipoSat_str = 'relu' then res:=CTS_ReLu
  else if TipoSat_str = 'max' then res:=CTS_Max
  else if TipoSat_str = 'smax' then res:=CTS_PonderSoftmax
  else raise Exception.Create( 'Tipo de neurona desconocido: ' + TipoSat_str_ );
  Result:=res;
end;




// Supone que a_ y b_ son instancias de TNeuronaConEntradas
// Retorna -1 si a < b, 0 si a = b y +1 si a > b
function CompareNeuronas( a_, b_: pointer ): integer;
var
  a, b: TNeuronaConEntradas;
  kRo, res: integer;
begin
  a:= TNeuronaConEntradas( a_ );
  b:= TNeuronaConEntradas( b_ );

  res := 0;

  if a.bias > b.bias then
  begin
    result := 1;
    exit;
  end;

  if a.bias < b.bias then
  begin
    result := -1;
    exit;
  end;

  for kRo := 0 to high(a.ro) do
  begin
    if a.ro[kRo] > b.ro[kRo] then
    begin
      res := 1;
      break;
    end
    else if a.ro[kRo] < b.ro[kRo] then
    begin
      res := -1;
      break;
    end;
  end;

  Result := res;
end;


end.
