martes, 10 de julio de 2007

Desarrollo de un seguidor de paredes inteligente (Parte 6 de 7: Implementación de un controlador difuso)

El controlador consta de varias clases que podrían haberse implementado en distintos ficheros. El motivo de haberlo hecho todo en el mismo .java se debe a facilitar la futura implementación en un Javelin, que sólo admite en principio un fichero.

Aún así analizaré una a una las clases, desde la más interna, al controlador final. Las clases son:
  • Etiqueta
  • Variable
  • Regla
  • Util
  • ControladorFuzzy

La clase Etiqueta representa el valor que puede tomar un componente de una variable difusa. Si por ejemplo tuviéramos la variable Altura con los posibles valores BAJA, MEDIA y ALTA, dichos valores serían etiquetas.
Por convenencia de proceso, representamos las etiquetas como variables triangulares. Dichas etiquetas pueden ser cortadas a una altura determinada durante la inferencia, para calcular los grados de emparejamiento. En ese caso, si al triángulo le cortamos el pico, tenemos un trapecio, por lo que necesitaremos dos valores de pico, para poder almacenar dichos valores.
El código es el siguiente:

private class Etiqueta{
public float izq;
public float pico;
public float pico2;
public float der;

public Etiqueta(float izq,float pico,float der){
this.izq = izq;
this.pico = this.pico2 = pico;
this.der = der;
}

public Etiqueta(float izq,float pico,float pico2, float der){
this.izq = izq;
this.pico = pico;
this.pico2 = pico2;
this.der = der;
}

public boolean igualA(float izq,float pico,float der){

if ((this.izq == izq) && (this.pico == pico) && (this.der == der))
return true;
else
return false;
}

public void imprimir(){
System.out.println("Izq: "+this.izq+" Pico :"+this.pico+" Pico2 :"+this.pico2+" Der :"+this.der);
}
}

La clase Variable sería para almacenar una variable difusa completa. Una variable tiene un valor mínimo, un valor máximo, una salida por defecto y un número de posibles etiquetas:

private class Variable{
public int nEtiquetas;
public float min;
public float max;
public float salidaPorDefecto;
public ArrayList etiquetas = new ArrayList();

public Variable(float min,float max,int nEtiquetas){
this.nEtiquetas = nEtiquetas;
this.min = min;
this.max = max;
this.salidaPorDefecto = (max + min)/2;
}

public void agregarEtiqueta(float izq,float pico,float der){
etiquetas.add(new Etiqueta(izq,pico,der));
}

public Etiqueta get(int i){
return (Etiqueta)etiquetas.get(i);
}

/** Dados unos valores de un triangulo devuelve
* el índice de la etiqueta si está en la variable
* y -1 si no está
* @param a Valor izquierdo
* @param b Valor central
* @param c Valor derecho
* @return El índice correspondiente
*/
public int esEtiqueta(float a,float b,float c){
Etiqueta temp;
for(int i=0;i<this.nEtiquetas;i++){
temp = this.get(i);
if (temp.igualA(a,b,c))
return i;
}
return -1;
}

public void imprimir(){
System.out.println("Número de etiquetas = "+nEtiquetas);
for(int i=0;i<nEtiquetas;i++){
System.out.println("Valor de la etiqueta "+(i+1)+": ");
this.get(i).imprimir();
}
}

}

La clase sería Regla. Una regla está formada por diversas variables, que pueden ser de estado (entrada) o de control (salida):

private class Regla{
int antecedentes[];
int consecuente;

public Regla(ArrayList datos){
antecedentes = new int[datos.size()];
for(int i=0;i<datos.size()-1;i++)
antecedentes[i] = ((Integer)datos.get(i)).intValue();

consecuente = ((Integer)datos.get(datos.size()-1)).intValue();

}

public int getAntecedente(int i){
try{
return antecedentes[i];
}catch(Exception ex){
return -1;
}
}
}


La clase Util, la he utilizado para declarar métodos que uso en diversas partes del código, como una clase accesibles por las demás, para tenerlo todo más ordenado:
private static class Util {

/** Lee un número real de un fichero de texto */
public static float leeFloat(FileInputStream f){
char c;
StringBuffer temp = new StringBuffer();
try{
c = (char)f.read();
while(!Character.isDigit(c) &&amp; (c != '-') && (c != '.')){
c = (char)f.read();
}
while (Character.isDigit(c) || (c == '-') || (c == '.')){
temp.append(c);
c = (char)f.read();
}

return Float.valueOf(new String(temp)).floatValue();

}catch(Exception ex){
System.out.println(ex.toString());
System.exit(1);
return 0;
}
}

/** Lee un número entero de un fichero de texto */
public static int leeEntero(FileInputStream f){
char c;
StringBuffer temp = new StringBuffer();
try{
c = (char)f.read();
while(!Character.isDigit(c) &&amp; (c != '-')){
c = (char)f.read();
}
while (Character.isDigit(c) || (c == '-')){
temp.append(c);
c = (char)f.read();
}
return Integer.valueOf(new String(temp)).intValue();


}catch(Exception ex){
System.out.println(ex.toString());
System.exit(1);
return 0;
}
}

/** Lee una palabra de un fichero de texto */
public static String leePalabra(FileInputStream f){
char c;
StringBuffer temp = new StringBuffer();
try{
c = (char)f.read();
while(!Character.isLetter(c)){
c = (char)f.read();
}
while (Character.isLetter(c)){
temp.append(c);
c = (char)f.read();
}


}catch(Exception ex){
System.out.println(ex.toString());
System.exit(1);
}
return new String(temp);
}

/** Trata de leer una cadena en un fichero */
public static boolean leeCadena(FileInputStream f,String cadena){
char c;
StringBuffer temp = new StringBuffer(cadena);
try{
for(int i=0;i<temp.length();i++){
c = (char)f.read();
if (c != temp.charAt(i))
return false;
}
}catch(Exception ex){
System.out.println(ex.toString());
System.exit(0);
}
return true;

}

}

La clase ControladorFuzzzy contiene todo lo necesario para un controlador difuso. Haciendo uso de las clases anteriores, es capaz de cargar todos los datos a partir de los archivos .wm y .pwm. Una vez cargados todos los valores en las estructuras, provee de métodos para la evaluación de datos externos. Comentaré los métodos uno a uno:
Contructor: Llama a los métidos cargaVariables y cargaReglas para cargar los datos del controlador en los atributos de la clase que son:
1.antecedentes: Una lista con las Variables de entrada del problema.
2.consecuente: La Variable de salida del problema.
3.reglas: Una lista con la base de Reglas del problema

cargaVariables: Toma el fichero .pwm y carga los datos sobre las variables del problema, tales como el universo de discurso, el número de etiquetas y el valor de los puntos de los triángulos para cada una de las etiquetas.

cargaReglas: Cada regla está formada por una etiqueta para cada una de las variables del problema. Este método toma el fichero .wm y lee los valores de los triángulos que conforman las etiquetas de cada regla. Si las etiquetas son valores válidos para las variables cargadas en el método anterior serán aceptadas y almacenadas. En caso contrario, se cancelará la operación.

Fuzzifica: Es el método encargado de tomar un dato de entrada y comprobar el emparejamiento con una etiqueta determinada. Dependiendo del punto en el que corte al triángulo, devolverá un valor entre 0 y 1.

evalua: Recibe como parámetros unos valores de entrada detectados por los sensores y calcula la salida adecuada.
Primero calcula los grados de emparejamiento de los datos con cada una de las reglas por la conjunción mediante la T-Norma del mínimo.
A continuación infiere los consecuentes para cada regla.
Para terminar, calcula el valor desfuzzificado mediante:


private class ControladorFuzzy{

private ArrayList antecedentes = new ArrayList();
private Variable consecuente;
private ArrayList reglas = new ArrayList();

public ControladorFuzzy(String datosVariables,
String datosReglas){

this.cargarVariables(datosVariables);
this.cargarReglas(datosReglas);
//DEBUG
/*
for(int i=0;i<antecedentes.size();i++){
((Variable)antecedentes.get(i)).imprimir();
}
consecuente.imprimir();
*/
}

private void cargarVariables(String fichero){
int nEtiquetas;
float min,max,izq,pico,der;
boolean fin = false;
String cadena;
Variable temp;
FileInputStream f = null;
try{
f = new FileInputStream(fichero);
while(!fin){
while (!Util.leeCadena(f,"Variable de "));
cadena = Util.leePalabra(f);
if (!cadena.equals("estado"))
fin = true;
while (!Util.leeCadena(f,"Numero de etiquetas = "));
nEtiquetas = Util.leeEntero(f);
while(!Util.leeCadena(f,"Universo de discurso = ["));
min = Util.leeFloat(f);
//while(!Util.leeCadena(f,","));
max = Util.leeFloat(f);
//while(!Util.leeCadena(f,"]"));
if (!fin)
antecedentes.add(new Variable(min,max,nEtiquetas));
else
consecuente = new Variable(min,max,nEtiquetas);
}

for(int i=0;i<antecedentes.size();i++){
temp = (Variable)antecedentes.get(i);
for(int j=0;j<temp.nEtiquetas;j++){
while(!Util.leeCadena(f,"("));
izq = Util.leeFloat(f);
pico = Util.leeFloat(f);
der = Util.leeFloat(f);
temp.agregarEtiqueta(izq,pico,der);
}

}
for(int j=0;j<consecuente.nEtiquetas;j++){
while(!Util.leeCadena(f,"("));
izq = Util.leeFloat(f);
pico = Util.leeFloat(f);
der = Util.leeFloat(f);
consecuente.agregarEtiqueta(izq,pico,der);
}



f.close();
}
catch(Exception ex){
System.out.println("Error de lectura"+ex.toString());

}
}

private void cargarReglas(String fichero){
FileInputStream f = null;
int nReglas = 0;
Variable v;
float valorIzquierdo = 0;
float valorCentral = 0;
float valorDerecho = 0;
ArrayList etiqueta = new ArrayList();

try{
f = new FileInputStream(fichero);

}catch(Exception ex){
System.out.println("Ocurrió un error con el fichero de reglas");
System.exit(1);
}

Util.leeCadena(f,"Numero de reglas: ");
nReglas = Util.leeEntero(f);

//Lectura de las reglas
for(int i=0;i<nReglas;i++){
for(int j=0;j<antecedentes.size()+1;j++){

valorIzquierdo = Util.leeFloat(f);
valorCentral = Util.leeFloat(f);
valorDerecho = Util.leeFloat(f);


if (j == antecedentes.size())
v = consecuente;
else{
v = (Variable)antecedentes.get(j);

}
etiqueta.add(new Integer(v.esEtiqueta(valorIzquierdo,valorCentral,valorDerecho)));
if (((Integer)etiqueta.get(j)).intValue() == -1){
System.out.println("Etiqueta no reconocida");
System.exit(1);
}
}

reglas.add(new Regla(etiqueta));
etiqueta.clear();
}

}



private float Fuzzifica(float X,Etiqueta D)
{

if ((X<D.izq) || (X&mt;D.der))
{
return (0);
}

if (X<D.pico)
return ((X-D.izq)*(1/(D.pico-D.izq)));

if (X&mt;D.pico2)
return ((D.der-X)*(1/(D.der-D.pico2)));
return (1);
}


public float evalua(ArrayList dat){

int i,j;
float aux,minimo,num,den,pico,pico2;
float gradoEmparejamiento[] = new float[reglas.size()];
int ndatos = dat.size();
float datos[] = new float[ndatos];
Variable x[] = new Variable[ndatos];
Etiqueta consecuentes[] = new Etiqueta[reglas.size()];
Etiqueta temp;

for(i=0;i<ndatos;i++){
datos[i] = ((Float)dat.get(i)).floatValue();
x[i] = ((Variable)antecedentes.get(i));
}

/* Conjunción mediante T-Norma del mínimo */

for(j=0;j<reglas.size();j++)
{
minimo = Float.MAX_VALUE;
for(i=0;i<ndatos;i++){
aux = this.Fuzzifica(datos[i],x[i].get( ((Regla)reglas.get(j)).getAntecedente(i)) );
if (aux<minimo)
minimo = aux;
}

gradoEmparejamiento[j] = minimo;
}

/* Inferencia de los consecuentes difusos para cada regla */
for(j=0;j<reglas.size();j++)
{
if (gradoEmparejamiento[j] != 0.0)
{
if (gradoEmparejamiento[j] == 1.0)
consecuentes[j] = consecuente.get(((Regla)reglas.get(j)).consecuente);
else
{
temp = consecuente.get(((Regla)reglas.get(j)).consecuente);
pico = temp.izq + (temp.pico - temp.izq)*gradoEmparejamiento[j];
pico2 = temp.der + (temp.pico2 - temp.der)*gradoEmparejamiento[j];
consecuentes[j] = new Etiqueta(temp.izq,pico,pico2,temp.der);
}
}
}

/* Desfuzzifico */
num = 0.0f;
den = 0.0f;
for(j=0;j<reglas.size();j++)
if (gradoEmparejamiento[j] != 0)
{
//System.out.println("Grado de emparejamiento "+ j+ " es "+gradoEmparejamiento[j]+".El valor acumulado ("+num+","+den+")" );
num += gradoEmparejamiento[j] * (consecuentes[j].pico+consecuentes[j].pico2)/2;
den += gradoEmparejamiento[j];
}
if (den!=0.0)
return (num/den);
else
return (consecuente.salidaPorDefecto);




}
}

No hay comentarios: