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)
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.
Suscribirse a:
Enviar comentarios (Atom)
Con la tecnología de Blogger.
Muy buen blog, se te agradece toda la información ya estoy tratando de ponerla en práctica. Thx
ResponderEliminarHola estoy haciendo ese mismo ejemplo en octave, me preguntaba que funciones de activación utilizaste en cada capa asi como en cuantas iteraciones lograste el resultado?
ResponderEliminarEn todas las capas he usado la función de activación tipo "sigmoidea":
ResponderEliminarfx = 1 / (1 + Math.pow(Math.E, -n));
Y número de iteracciones realizadas:
int nTests = 1000000;
Gracias por blog, saber si todo este código esta github
ResponderEliminar