Una forma sencilla y rápida de aprender JAVA, observando y deduciendo cómo se comporta el lenguaje a través de ejemplos prácticos.

domingo, 6 de mayo de 2018

Perceptrón multicapa. Aprendiendo a sumar (borrador)

Esquema general de una red neuronal con estructura {2, 3, 3, 1} (perceptrón multicapa), con sus pesos y su propia nomenclatura.





En este ejemplo trata de "aprender" la operación de sumar teniendo como única referencia la siguiente tabla de verdad:




A grandes rasgos el método utilizado para resolver el problema es la de modificar un peso aleatoriamente aplicándole una tasa de aprendizaje (ts) de 0.001.
Si modificado un peso aleatorio (sumándole 0.001) notamos que disminuye el error, se mantendrá el peso con la modificación realizada anteriormente y pasará al siguiente ciclo. De lo contrario si aumenta el error el valor del peso vuelve a su valor anterior al cambio y así nos aseguramos que el sistema no empeora. Así que por cada ciclo realizado el sistema tiende a mejorar el resultado.



Código 1 (Red.java):

package red;

import java.util.Arrays;
import java.util.List;
import java.util.Scanner;

public class Red {

   public static void main(String[] args) {

      // arquitectura perceptron     
      int nK[] = {2, 4, 1};
      int nRegistros = 10;
      int nTests = 1000000;
      double tasaAprendizaje = 0.001;

      TablaSuma tv = new TablaSuma(nRegistros, 3);

      // TESTS:
      double[] vEntradaTest = new double[nK[0]];

      // tabla de entrenamiento fija (sumas)
      double tablaVerdad[][] = tv.getTv();

      // inicializar pesos (aleatorios)
      InicializarPesos iniciarPesos = new InicializarPesos(nK, 0, 1);
      List<Double> listaPesos = iniciarPesos.getPesos();

      // - GESTIÓN TABLA DE VERDAD -
      DescomponerTabla dt = new DescomponerTabla(nK, tablaVerdad);
      mostrarTablaEntrenamiento(nRegistros, dt);

      // - CALCULOS SALIDAS -
      PropagacionAdelante pd = new PropagacionAdelante();
      pd.sets(nK, listaPesos, dt.getEntradas(0), 0); // nRegistro=0, op=0 -> tipo funcion

      // calculo del vError:
      double err[] = new double[tablaVerdad.length];

      // inicializar min a valores maximos
      double minTotal = Double.MAX_VALUE;
      double maxTotal = Double.MIN_VALUE;

      pd.run();

      // variables basicas
      int indice;
      double pesoNuevo;

      // inicializar variables
      double tSalidaDeseada[][] = dt.getTablaSalidasDeseadas();
      double vSalidaReal[] = pd.getSalidasFinales();
      double vError[] = new double[vSalidaReal.length];
      double errorMedio[] = new double[nRegistros];
      double errorMedioTotal;

      System.out.println("\n01- Iniciando fase de aprendizaje...");
      for (int t = 0; t < nTests; t++) { // numero de tests...

         indice = (int) (Math.random() * (listaPesos.size())); // escoje un peso aleatorio
         pesoNuevo = listaPesos.get(indice);
         listaPesos.set(indice, pesoNuevo - (tasaAprendizaje * 2)); // actualiza peso

         for (int nRegistro = 0; nRegistro < nRegistros; nRegistro++) { // numero registros...
            pd.setPesos(listaPesos);
            pd.setEntradas(dt.getEntradas(nRegistro)); // row
            pd.run();
            vSalidaReal = pd.getSalidasFinales();

            // calcular error medio
            double tmp = 0;
            for (int i = 0; i < vError.length; i++) { // numero salidas finales del registro x
               vError[i] = tSalidaDeseada[nRegistro][i] - (vSalidaReal[i] * 100); // row = 0
               tmp = tmp + Math.abs(vError[i]);
            }
            errorMedio[nRegistro] = tmp / vError.length;
         }

         // calculo error medio total
         errorMedioTotal = 0;
         for (int i = 0; i < errorMedio.length; i++) {
            errorMedioTotal += Math.abs(errorMedio[i]);
         }
         errorMedioTotal = errorMedioTotal / errorMedio.length;

         // Error minimo Total
         if (errorMedioTotal < minTotal) {
            minTotal = errorMedioTotal;
         } else {
            listaPesos.set(indice, pesoNuevo); // restaura peso anterior
         }

         if (errorMedioTotal > maxTotal) {
            maxTotal = errorMedioTotal;
         }

      }

      // Fase de testeo.
      System.out.println("02- Iniciando fase de Testeo...");

      double sDeseada;
      double[] real;
      double confiable;
      double aux1 = 0;
      int nTestsConfiabilidad = 100;
      for (int i = 0; i < nTestsConfiabilidad; i++) {
         vEntradaTest[0] = (Math.random() * 50);
         vEntradaTest[1] = (Math.random() * 50);
         pd.sets(nK, listaPesos, vEntradaTest, 0); // nRegistro=0, op=0 -> tipo
         pd.run();
         real = pd.getSalidasFinales();
         sDeseada = vEntradaTest[0] + vEntradaTest[1];
         confiable = (((Math.abs(real[0] * 100.0 - sDeseada)) * 100.0) / sDeseada) - 100.0;
         aux1 += confiable;
      }
      System.out.println("03- Resultado confiabilidad %:");
      double conf = Math.abs(aux1 / nTestsConfiabilidad);
      System.out.println(conf + "%");

      // Fase de testeo manual.
      System.out.println("\nIniciando fase de testeo manual...");
      System.out.println("Introduce entrada 1: ");
      Scanner leerX1 = new Scanner(System.in);
      vEntradaTest[0] = Double.parseDouble(leerX1.next());
      System.out.println("Introduce entrada 2: ");
      Scanner leerX2 = new Scanner(System.in);
      vEntradaTest[1] = Double.parseDouble(leerX2.next());
      pd.sets(nK, listaPesos, vEntradaTest, 0);
      pd.run();
      real = pd.getSalidasFinales();
      double ia = real[0] * 100.0;
      double iafinal = (conf * ia) / 100;
      System.out.println("Salida = " + iafinal);

   }

   private static void mostrarTablaEntrenamiento(int nFilas, DescomponerTabla dt) {
      System.out.println("Tabla de referencia para el entrenamiento:");
      for (int i = 0; i < nFilas; i++) {
         System.out.println(""
                 + Arrays.toString(dt.getEntradas(i)) + "\t "
                 + Arrays.toString(dt.getSalidas(i)));
      }
   }

}


Código 2 (
TablaSuma.java):

package red;

class TablaSuma {

   final double[][] tv;

   TablaSuma(int nFilas, int col) {

      this.tv = new double[nFilas][col];
     
      // Tabla de verdad suma
      tv[0][0] = 48.0; tv[0][1] = 33.0; tv[0][2] = 81.0;
      tv[1][0] =  1.0; tv[1][1] = 38.0; tv[1][2] = 39.0;
      tv[2][0] = 41.0; tv[2][1] = 25.0; tv[2][2] = 66.0;
      tv[3][0] =  6.0; tv[3][1] = 27.0; tv[3][2] = 33.0;
      tv[4][0] =  5.0; tv[4][1] = 42.0; tv[4][2] = 47.0;
      tv[5][0] = 18.0; tv[5][1] = 12.0; tv[5][2] = 30.0;
      tv[6][0] = 35.0; tv[6][1] = 39.0; tv[6][2] = 74.0;
      tv[7][0] =  2.0; tv[7][1] = 17.0; tv[7][2] = 19.0;
      tv[8][0] = 44.0; tv[8][1] = 14.0; tv[8][2] = 58.0;
      tv[9][0] = 24.0; tv[9][1] = 37.0; tv[9][2] = 61.0;

   }

   public double[][] getTv() {
      return tv;
   }

}


Código 3 (Mostrar.java):

package red;

import java.util.Iterator;
import java.util.List;

class Mostrar {

   Mostrar(int[] nK, List<Double> listaPesos, List<Double> listaSalidas) {
      Iterator pesos = listaPesos.iterator();
      Iterator salidas = listaSalidas.iterator();

      // mostrar entradas
      int k = 0;
      System.out.println("\n* Capa(k): " + k);
      for (int i = 0; i < nK[0]; i++) {
         System.out.println("s[" + k + "][" + i + "] = " + (double) salidas.next());
      }

      for (k = 1; k < nK.length; k++) {
         System.out.println("\n* Capa(k): " + k);
         for (int i = 0; i < nK[k]; i++) {
            for (int j = 0; j < nK[k - 1]; j++) {
               System.out.println("w[" + k + "][" + j + "][" + i + "] = " + (double) pesos.next());
            }
            System.out.println("s[" + k + "][" + i + "] = " + (double) salidas.next());
            System.out.println("");
         }
      }
   }
}


Código 4 (DescomponerTabla.java):

package red;

class DescomponerTabla {

   int[] nK;
   double[][] tVerdad;
   double[][] tEntradas;
   double[][] tSalidasDeseadas;

   int nEntradas, nSalidas;

   public DescomponerTabla(int[] nK, double[][] tablaVerdad) {

      this.nK = nK;
      this.tVerdad = tablaVerdad;

      nEntradas = nK[0];
      nSalidas = nK[nK.length - 1];
      int nFilas = tablaVerdad.length;

      tEntradas = new double[nFilas][nEntradas];
      for (int i = 0; i < nFilas; i++) {        
         System.arraycopy(tablaVerdad[i], 0, tEntradas[i], 0, nEntradas);
      }

      tSalidasDeseadas = new double[nFilas][nSalidas];
      for (int i = 0; i < nFilas; i++) {
         for (int j = 0; j < nSalidas; j++) {
            tSalidasDeseadas[i][j] = tablaVerdad[i][nEntradas + j];
         }
      }

   }

   public double[][] getTablaEntradas() {
      return tEntradas;
   }

   public double[][] getTablaSalidasDeseadas() {
      return tSalidasDeseadas;
   }

   public double[] getEntradas(int fila) {
      double[] vEntrada = new double[nEntradas];
      System.arraycopy(tEntradas[fila], 0, vEntrada, 0, nEntradas);
      return vEntrada;
   }

   public double[] getSalidas(int fila) {
      double[] vSalida = new double[nSalidas];
      System.arraycopy(tSalidasDeseadas[fila], 0, vSalida, 0, nSalidas);
      return vSalida;
   }

}


Código 5 (InicializarPesos.java):

package red;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

class InicializarPesos {

   List<Double> listaPesos = new ArrayList<>();

   InicializarPesos(int[] nK, int n, int m) {

      double[][][] w = new double[nK.length][nPesos(nK)][nPesos(nK)];
      double[][] s = new double[nK.length][nNeuronas(nK)];

      for (int k = 1; k < nK.length; k++) {
         for (int i = 0; i < nK[k]; i++) {
            for (int j = 0; j < nK[k - 1]; j++) {
               w[k][j][i] = new Random().nextDouble() * (n - m) + m;
//               w[k][j][i] = new Random().nextDouble();
               listaPesos.add(w[k][j][i]);
            }
         }
      }
   }

   public List<Double> getPesos() {
      return listaPesos;
   }

   // calcular cantidad de pesos necesarios
   private int nPesos(int[] nK) {
      int nPesos = 1;
      for (int i = 0; i < nK.length; i++) {
         nPesos *= nK[i];
      }
      return nPesos;
   }

   // calcular cantidad de neuronas necesarias
   private int nNeuronas(int[] nK) {
      int nNeuronas = 1;
      for (int i = 0; i < nK.length; i++) {
         nNeuronas += nK[i];
      }
      return nNeuronas;
   }

}


Código 6 (PropagacionAdelante.java):

package red;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

final class PropagacionAdelante {

   private int[] nK;
   private Iterator ipesos;
   private double[] entradas;
   private int op;
   private List<Double> listaSalidas;

   PropagacionAdelante() {
   }

   public void sets(int[] nK, List<Double> listaPesos, double[] entradas, int op) {
      listaSalidas = new ArrayList<>();
      this.nK = nK;
      this.ipesos = listaPesos.iterator();
      this.entradas = entradas;
      this.op = op; // tipo de función

   }

   public void setPesos(List<Double> listaPesos) {
      this.ipesos = listaPesos.iterator();
   }

   public void setEntradas(double[] entradas) {
      this.entradas = entradas;
   }

   public void run() {
      double[][] s = new double[nK.length][nNeuronas(nK)];
      listaSalidas.clear();
      // ENTRADAS:
      int k = 0;
      for (int i = 0; i < entradas.length; i++) {
         s[k][i] = entradas[i] / 100.0;
         listaSalidas.add(s[k][i]);
      }
      // SALIDAS:
      double tmp;
      for (k = 1; k < nK.length; k++) {
         for (int i = 0; i < nK[k]; i++) {
            tmp = 0.0;
            for (int j = 0; j < nK[k - 1]; j++) {
               tmp += s[k - 1][j] * (double) ipesos.next();
            }
            s[k][i] = fx(tmp, op);
            listaSalidas.add(s[k][i]);
         }
      }
   }

   public List<Double> getSalidas() {
      return listaSalidas;
   }

   public double[] getSalidasFinales() { // no me gusta nada, pero funciona...
      double vSalida[] = new double[nK[nK.length - 1]];
      List<Double> aux = listaSalidas.subList(listaSalidas.size() - nK[nK.length - 1], listaSalidas.size());
      for (int i = 0; i < aux.size(); i++) {
         vSalida[i] = aux.get(i);
      }
      return vSalida;
   }

   // METODOS ------------------------------------------------------------------
  
// funcion de activación(F)

   public double fx(double n, int op) {
      double fx = 0;
      switch (op) {
         case 0: // (0,1)
            fx = 1 / (1 + Math.pow(Math.E, -n));
            break;
         case 1: // (-1,1)
            fx = Math.tanh(n);
            break;
      }
      return fx;
   }

   // calcular cantidad de neuronas necesarias
   private int nNeuronas(int[] nK) {
      int nNeuronas = 1;
      for (int i = 0; i < nK.length; i++) {
         nNeuronas += nK[i];
      }
      return nNeuronas;
   }

}



Resultado:

run:
Tabla de referencia para el entrenamiento:
[48.0, 33.0]     [81.0]
[1.0, 38.0]     [39.0]
[41.0, 25.0]     [66.0]
[6.0, 27.0]     [33.0]
[5.0, 42.0]     [47.0]
[18.0, 12.0]     [30.0]
[35.0, 39.0]     [74.0]
[2.0, 17.0]     [19.0]
[44.0, 14.0]     [58.0]
[24.0, 37.0]     [61.0]

01- Iniciando fase de aprendizaje...
02- Iniciando fase de Testeo...
03- Resultado confiabilidad %:
81.81628816341885%

Iniciando fase de testeo manual...
Introduce entrada 1:
22
Introduce entrada 2:
14
Salida = 36.71309818869402
BUILD SUCCESSFUL (total time: 37 seconds)


domingo, 19 de noviembre de 2017

Creación de una estructura de red neuronal artificial perceptrón multicapa.

Se trata de que a partir de un vector se cree una estructura neuronal artificial basada en capas (perceptrón multicapa).
El funcionamiento es que los valores introducidos en el vector indican el número de neuronas que tiene cada capa, y la posición o el índice de estos valores corresponde a la capa a la que se aplica.

Ejemplo:

Con el vector {2, 3, 3, 1} la red tendría la siguiente estructura:





En el ejemplo también se agregan los pesos(w) de las conexiones y se calculan todas las salidas de las neuronas(s). El cálculo de las salidas se le llama "propagación hacia delante".


* Nomenclatura de los pesos(w):

w[k][j][i]

k = índice capa
j = índice conector destino
i = índice conector origen


* Nomenclatura de las salidas(s):

s[k][j]

k = índice capa
j = índice neurona

En última instancia se calcula el error, teniendo como referencia la salida deseada (d1) en contraste con la salida real.


Código1 (Red.java):

package red;
public class Red {
   public static void main(String[] args) {
      Estructura red = new Estructura();
      red.estruct();
   }
}


Código2 (Estructura.java):

package red;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

public class Estructura {

   int nK[] = {2, 3, 3, 1};
   double[][][] w = new double[nK.length][9][9]; //peso
   double[][] s = new double[nK.length][9]; //salida

   // tabla de entrenamiento
   final int x1[] = {34}; // entrada 1 (x1)
   final int x2[] = {23}; // entrada 2 (x2)
   final int d1[] = {57}; // salida deseada 1 (d1)

   public void estruct() {

      int row = 1;
      for (int i = 0; i < nK.length; i++) {
         row *= nK[i];
      }

      // inicializar vector con pesos aleatorios.
      double[] vw = new double[row];
      for (int i = 0; i < vw.length; i++) {
         vw[i] = new Random().nextDouble();
      }

      // Añadir los pesos(w) y cálculo salidas(s) - Propagación hacia delante -
      propagacion_hacia_delante(vw);

   }

   private void propagacion_hacia_delante(double[] vw) {

      // vector a lista  
      List<Double> lw = new ArrayList<>();
      for (int i = 0; i < vw.length; i++) {
         lw.add(vw[i]);
      }
      Iterator wi = lw.iterator();

      System.out.println("* Entradas (x1, x2): ");
      int k = 0;
      s[k][0] = x1[0] / 100.0;
      s[k][1] = x2[0] / 100.0;
      System.out.println("s[" + k + "][" + 0 + "] = " + s[k][0]);
      System.out.println("s[" + k + "][" + 1 + "] = " + s[k][1]);
      double aux;
      for (k = 1; k < nK.length; k++) {
         System.out.println("\n* Capa(k): " + k);
         for (int j = 0; j < nK[k]; j++) {
            aux = 0.0;
            for (int i = 0; i < nK[k - 1]; i++) {
               if (wi.hasNext()) {
                  w[k][j][i] = (double) wi.next();
                  System.out.println("w[" + k + "][" + j + "][" + i + "] = " + w[k][j][i]);
                  aux += s[k - 1][i] * w[k][j][i];
               }
            }
            s[k][j] = F(aux);
            System.out.println("s[" + k + "][" + j + "] = " + s[k][j]);
            System.out.println("");
         }
      }

      double error = error(s[nK.length - 1][0]);
      System.out.println("Error(%) = " + error + "\n");

   }

   // cálculo error
   private double error(double salida) {
      System.out.println("\nCalculando error: ");
      System.out.println("Salida real (s) = " + salida);
      System.out.println("Salida deseada (d1) = " + d1[0] / 100.0);
      double error = (d1[0] / 100.0) - salida;
      return Math.abs(error) * 100.0;
   }

   // función de activación(F)
   public double F(double n) {
      return 1 / (1 + Math.pow(Math.E, -n));
   }

}


Resultado:

run:

* Entradas (x1, x2):
s[0][0] = 0.34
s[0][1] = 0.23

* Capa(k): 1
w[1][0][0] = 0.8626655902899227
w[1][0][1] = 0.6463931000086637
s[1][0] = 0.6087299411252262

w[1][1][0] = 0.7796971368969626
w[1][1][1] = 0.5490805855405314
s[1][1] = 0.5966162004085603

w[1][2][0] = 0.009961780486465899
w[1][2][1] = 0.9279270075726691
s[1][2] = 0.5539912241475227


* Capa(k): 2
w[2][0][0] = 0.34979295784506204
w[2][0][1] = 0.2971577727858278
w[2][0][2] = 0.18553139517021167
s[2][0] = 0.6208132190214526

w[2][1][0] = 0.43931905607836685
w[2][1][1] = 0.20711093913624445
w[2][1][2] = 0.4093740271593507
s[2][1] = 0.6497139364472229

w[2][2][0] = 0.9188418704894066
w[2][2][1] = 0.027621699012403522
w[2][2][2] = 0.9216813528324754
s[2][2] = 0.7477050027136913


* Capa(k): 3
w[3][0][0] = 0.4759577050326803
w[3][0][1] = 0.6521570348684057
w[3][0][2] = 0.07185252070446402
s[3][0] = 0.6841523907997876


Calculando error:
Salida real (s) = 0.6841523907997876
Salida deseada (d1) = 0.57
Error(%) = 11.415239079978768



BUILD SUCCESSFUL (total time: 0 seconds)


domingo, 23 de julio de 2017

Serie armónica.

La serie armónica es la suma de los inversos de los números naturales.











Código java (numeroArmonico.java):

// Serie armónica hasta el 16
package numeroarmonico;

public class SerieArmonica {
   public static void main(String[] args) {
      int n = 16;
      double sum = 0.0;
      for (int k = 1; k <= n; k++) {
         sum += 1.0 / k;
         System.out.printf("H(%d) = %f\n", k, sum);
      }
   }
}




Resultado:

run:
H(1) = 1,000000
H(2) = 1,500000
H(3) = 1,833333
H(4) = 2,083333
H(5) = 2,283333
H(6) = 2,450000
H(7) = 2,592857
H(8) = 2,717857
H(9) = 2,828968
H(10) = 2,928968
H(11) = 3,019877
H(12) = 3,103211
H(13) = 3,180134
H(14) = 3,251562
H(15) = 3,318229
H(16) = 3,380729
BUILD SUCCESSFUL (total time: 0 seconds)


domingo, 19 de marzo de 2017

Discretización basada en Entropía (I).

La entropía calcula el grado de desorden de un sistema. En este ejemplo será calculada en base a dos tipos de clases (A, B).
En minería de datos se utiliza para encontrar la partición con la máxima ganancia de información posible. 



* Procedimiento:

Sea S un conjunto de 1000 pares (AtributoValor, Clase) por ejemplo:

 
S = {(15, A), (280, B), (319, A), (775, B), (801, B), (850, A), (855, B), (880, B)....}

p1 = núm. de atributos de A / 1000 -> (la fracción de pares con clase = A)
p2 = núm. de atributos de B / 1000 -> (la fracción de pares con clase = B)

La entropía (o contenido de información) para S es:

    Entropia(S) = - p1 * log2(p1) – p2 * log2(p2)


* Características resultado:

    - Nos tiene que dar un valor entre 0 y 1.
    - Si la entropía es pequeña, entonces el conjunto esta muy ordenado.
    - Si la entropía es grande, entonces el conjunto esta muy desordenado.
    - En este ejemplo al obtener las clases aleatoriamente, nos debería dar una entropía alta, ya que lo aleatorio es sinónimo de desorden.


Código java (DiscretizarEntropia.java):

package discretizarentropia;

public class DiscretizarEntropia {

   public static void main(String[] args) {

      int nRegistros = 1000;
      Object[][] v = new Object[nRegistros][2];

      int cont = 0;
      for (int i = 0; i < nRegistros; i++) {
         v[i][0] = (int) (Math.random() * 1000);
         v[i][1] = (int) (Math.random() * 2);
         if (v[i][1].equals(0)) {
            cont++;
         }
      }

      double p1 = (float) cont / (float) nRegistros;
      double p2 = ((float) nRegistros - (float) cont) / (float) nRegistros;


      double S = -(p1 * log(p1, 2)) - (p2 * log(p2, 2));

      System.out.println("Número de clases A: " + cont);
      System.out.println("Número de clases B: " + (nRegistros - cont));
      System.out.println("Fracción de pares con clase A (p1) = " + p1);
      System.out.println("Fracción de pares con clase B (p2) = " + p2);
      System.out.println("Entropia(S) = " + S);

   }

   private static Double log(double num, int base) {
      return (Math.log10(num) / Math.log10(base));
   }

}


Resultado:

run:
Número de clases A: 481
Número de clases B: 519
Fracción de pares con clase A (p1) = 0.48100000619888306
Fracción de pares con clase B (p2) = 0.5189999938011169
Entropia(S) = 0.9989581240309595
BUILD SUCCESSFUL (total time: 0 seconds)



Con la tecnología de Blogger.