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)


Con la tecnología de Blogger.