martes, 27 de mayo de 2014

Implementación en Java de un perceptrón simple

Un perceptrón es la unidad básica de inferencia, siendo capaz de realizar tareas de clasificación de forma automática. En la red podemos encontrar bastante información relacionada al perceptrón simple, sin embargo en este artículo vamos a poner énfasis en su implementación utilizando el lenguaje Java. La arquitectura básica de un perceptrón es la que se muestra a continuación:


Vamos a describir el algoritmo que emplearemos para implementar nuestro perceptrón simple. En primer lugar vamos a conocer cuáles son las entradas; la siguiente imagen muestra los inputs de nuestro algoritmo.


La constante de proporcionalidad η es un parámetro positivo que se llama tasa de aprendizaje puesto que cuanto mayor es, más se modifica el peso sináptico y viceversa. Es decir, es el parámetro que controla el proceso de aprendizaje. Cuando es muy pequeño la red aprende poco a poco. Cuando se toma constante en todas las iteraciones, η(k) =η > 0 tendremos la regla de adaptación con incremento fijo.

También podemos notar que tanto los vectores de pesos como de valores de entrada tienen como cantidad de elementos a n+1. Esto se debe a que hemos agregado al nivel de umbral como una entrada más. El umbral se puede considerar como el peso sináptico correspondiente a un nuevo sensor de entrada que tiene siempre una entrada igual a Xn+1=−1 y como peso sináptico el valor del umbral.

Ahora vamos a empezar con la inicialización de variables. Recordemos que es muy importante incializar nuestro vector de pesos siempre con valores diferentes en cada ejecución del programa.


Y el proceso queda descrito de la siguiente manera:


Y ahora la codificación:
public class Perceptron {

    private double[] pesos;
    private double[] objetivos;
    private double[][] entradas;
    private int numeroEntradas;
    private static final double TASA_APRENDIZAJE = 0.5d;

    public double[][] getEntradas() {
        return entradas;
    }

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

    public double[] getObjetivos() {
        return objetivos;
    }

    public void setObjetivos(double[] objetivos) {
        this.objetivos = objetivos;
    }

    public double[] getPesos() {
        return pesos;
    }

    public void setPesos(double[] pesos) {
        this.pesos = pesos;
    }

    /**
     * Inicializar los pesos sinápticos con números aleatorios del intervalo [-1, 1]
     */
    public void inicializarPesos() {
        pesos = new double[numeroEntradas];
        for (int i = 0; i < numeroEntradas; i++) {
            pesos[i] = Math.random();
        }
    }

    public void imprimirPesos() {
        for (int i = 0; i < numeroEntradas; i++) {
            System.out.println("W[" + i + "] = " + pesos[i]);
        }
    }

    /**
     * wj(k+1)=wj(k)+&#951;[z(k)&#8722;y(k)]xj(k), j =1,2,...,n+1
     */
    public void recalcularPesos(int posicionEntrada, double y) {
        for (int i = 0; i < pesos.length; i++) {
            pesos[i] = pesos[i] + TASA_APRENDIZAJE * (objetivos[posicionEntrada] - y) * entradas[posicionEntrada][i];
        }
    }

    public void entrenar() {
        int indice = 0;
        double yi = 0;
        while (indice < entradas.length) {
            double suma = 0;
            for (int i = 0; i < numeroEntradas; i++) {
                suma += (pesos[i] * entradas[indice][i]);//&#8721; x[i] * W[i] 
            }
            yi = suma >= 0 ? 1 : -1;
            if (yi == objetivos[indice]) {
                //Correcto
                for (int i = 0; i < numeroEntradas; i++) {
                    System.out.print(entradas[indice][i] + "t");
                }
                System.out.print(" => Esperada = " + objetivos[indice] + ", Calculada = " + yi + "n");
            } else {
                //Incorrecto
                for (int i = 0; i < numeroEntradas; i++) {
                    System.out.print(entradas[indice][i] + "t");
                }
                System.out.print(" => Esperada = " + objetivos[indice] + ", Calculada = " + yi + " [Error]n");
                System.out.println("Corrección de pesos");
                recalcularPesos(indice, yi);
                imprimirPesos();
                System.out.println("--");
                indice = -1;
            }
            indice++;
        }
    }
}
Ahora hagamos una prueba con entrenando a nuestro perceptrón para que reconozca la función lógico OR, tal como se tabula a continuación:

Para esto desarrollamos una clase de prueba.
public class Test {

    public static void main(String[] args) {
        Perceptron p = new Perceptron();
        //Salidas esperadas
        double[] objetivos = {1, 1, 1, -1};
        //Entradas
        //x1, x2, &#952;
        double[][] entradas = {
            {1, 1, -1},
            {1, -1, -1},
            {-1, 1, -1},
            {-1, -1, -1}
        };
        p.setEntradas(entradas);
        p.setObjetivos(objetivos);
        p.inicializarPesos();
        p.entrenar();
        System.out.println("********** Pesos Finales **********");
        p.imprimirPesos();
    }
}
Obteniendo el siguiente resultado:
1.0 1.0 -1.0  => Esperada = 1.0, Calculada = 1.0
1.0 -1.0 -1.0  => Esperada = 1.0, Calculada = -1.0 [Error]
Corrección de pesos
W[0] = 1.9944020684085446
W[1] = -0.4272180776608493
W[2] = -0.3794857322622548
--
1.0 1.0 -1.0  => Esperada = 1.0, Calculada = 1.0
1.0 -1.0 -1.0  => Esperada = 1.0, Calculada = 1.0
-1.0 1.0 -1.0  => Esperada = 1.0, Calculada = -1.0 [Error]
Corrección de pesos
W[0] = 0.9944020684085446
W[1] = 0.5727819223391507
W[2] = -1.3794857322622547
--
1.0 1.0 -1.0  => Esperada = 1.0, Calculada = 1.0
1.0 -1.0 -1.0  => Esperada = 1.0, Calculada = 1.0
-1.0 1.0 -1.0  => Esperada = 1.0, Calculada = 1.0
-1.0 -1.0 -1.0  => Esperada = -1.0, Calculada = -1.0
********** Pesos Finales **********
W[0] = 0.9944020684085446
W[1] = 0.5727819223391507
W[2] = -1.3794857322622547
Con los pesos finales, tenemos a nuestra red neuronal entrenada. Los campos de aplicación del perceptrón simple son muy reducidos pues su aprendizaje llega a un estado de convergencia solo cuando los elementos separables y los no separables pueden ser llevados a un plot en el cual puedan ser separados por un hiperplano. En la siguiente imagen, el caso a sí puede ser aprendido por un perceptrón simple mientras que el caso b no podría ser aprendido por un solo perceptrón. 


Espero que este articulo les sea de utilidad. Cualquier consulta, no duden en comentarla.

4 comentarios:

Anónimo dijo...

Excelente, de casualidad llegue a tu blog. Muito interesante. Me aclarastes muchos concepto. Gracias

Anónimo dijo...

Hola Que tal, trato de compilar el programa pero no aparece nada.
Podras dejar el codigo para descar.
Muchas Gracias.

Felipe Martins dijo...

dodnde esta el la variable y en los parametros en la funcion recalcularPesos

Felipe Martins dijo...

que seria el X3?

Publicar un comentario