Fuzzy Logic y expresiones lambda

Para un proyecto que estoy realizando, he realizado una prueba de
concepto probado a aplicar lógica difusa con el fin de realizar ciertas
acciones a partir de una serie de datos imprecisos.

A la hora de expresar las reglas encargadas de modelar nuestro
sistema de lógica difusa, (IF-THEN) la mayoría de los sistemas que he
visto requerían de un pequeño y simple analizador sintáctico para
interpretar las reglas, en este punto me he preguntado si en vez de un
analizador sintáctico, podríamos usar expresiones lambda y una interfaz
fluida.

FuzzySystem (improvisado)

De
modo que he construido un pequeño motor de lógica difusa que tiene la
particularidad de usar expresiones lambda y una interfaz fluida para
definir el conjunto de reglas.
Este pequeño motor incorpora un par
de funciones miembro, una de tipo Trapezoide y otra Triangular,
básicamente su uso es el siguiente.
Definimos el sistema, añadimos las variables con sus estados y funciones miembro…

01 public class FuzzyTest
02 {
03     private readonly FuzzySystem _engine = new FuzzySystem();
04  
05     public FuzzyTest()
06     {
07         FuzzyVariable varA = new FuzzyVariable("A");
08         varA.Memberships.Add(new TrapezoidMembership("Cerrado", 0, 0, 5, 5));
09         varA.Memberships.Add(new TrapezoidMembership("Medio", 4, 4, 6, 6));
10         varA.Memberships.Add(new TrapezoidMembership("Abierto", 7, 7, 9, 9));
11         _engine.Variables.Add(varA);
12  
13         FuzzyVariable varB = new FuzzyVariable("B");
14         varB.Memberships.Add(new TrapezoidMembership("Cerrado", 0, 0, 5, 5));
15         varB.Memberships.Add(new TrapezoidMembership("Medio", 4, 4, 6, 6));
16         varB.Memberships.Add(new TrapezoidMembership("Abierto", 7, 7, 9, 9));
17         _engine.Variables.Add(varB);
18  
19         FuzzyVariable varR = new FuzzyVariable("R");
20         varR.Memberships.Add(new TrapezoidMembership("Frio", 0, 0, 3, 3));
21         varR.Memberships.Add(new TrapezoidMembership("Templado", 3, 3, 6, 6));
22         varR.Memberships.Add(new TrapezoidMembership("Caliente", 7, 7, 9, 9));
23         _engine.Variables.Add(varR);

 

La definición de reglas mediante expresiones Lambda

El
beneficio en este punto es que el sistema no requiere del analizador
sintáctico para evaluar las expresiones si no que será el CLR quien se
encargue de ello.
Para llevar a cabo esta evaluación de las reglas he definido una clase FuzzyExpresion que
tiene definidos  los operadores && y || para así como el true y
el false para poder evaluar las reglas en consecuencia.

Las reglas en la lógica difusa sirven para combinar las distintas
proposiciones, no son reglas excesivamente complicadas, y son del tipo
IF-THEN, básicamente hay 4 reglas (Implicación Conjunción, Disyunción 
y negación) que se corresponden con:

–    Implicación IF variable==estado THEN variable = estado
–    Conjunción, equivalente al AND, si dos proposiciones son ciertas simultáneamente
–    Disyunción, cualquiera de las dos proposiciones es cierta OR
–    Negación, invierte la proposición

Finalmente podemos definir las reglas de este modo

01 _engine.AddRule(x =>
02                 x.Variable("A").Is("Abierto") &&
03                 x.Variable("B").Is("Abierto"))
04                 .Then(x => x.Variable("R").Set("Caliente"));
05  
06 _engine.AddRule(x =>
07                 x.Variable("A").Is("Abierto") &&
08                 (x.Variable("B").Is("Cerrado") || x.Variable("B").Is("Medio")))
09                 .Then(x => x.Variable("R").Set("Templado"));
10  
11 _engine.AddRule(x =>
12                 x.Variable("B").Is("Abierto") &&
13                 (x.Variable("A").Is("Cerrado") || x.Variable("A").Is("Medio")))
14                 .Then(x => x.Variable("R").Set("Templado"));
15  
16 _engine.AddRule(x =>
17                 x.Variable("A").Is("Cerrado") &&
18                 x.Variable("B").Is("Cerrado"))
19                 .Then(x => x.Variable("R").Set("Frio"));

¿Cómo funciona?

Una regla tiene una condición (IF) y una consecuencia (THEN), la
condición es una función que recibe el sistema y debe devolver una
FuzzyExpresion, (Func<FuzzySystem,FuzzyExpression>),
esto es parte de la magia ya que una expresión condicional es siempre
reducida a una única expresión y finalmente a un único valor.
Y
curiosamente la consecuencia de la regla, es exactamente igual, solo
que en este caso la expresión no devuelve nada porque la consecuencia
es una acción, aquí hay otro pequeño truco que es que la propia regla
contiene los dos elementos separados (condición y consecuencia) fijaros
en que:

1 _engine.AddRule(x =>
2                      x.Variable("A").Is("Abierto") &&
3                      x.Variable("B").Is("Abierto"))

Está devolviendo una regla y el método Then es aplicado
sobre la regla, y cuando se evalúa la regla si la condición es cierta
se evalúa la consecuencia, que lo que hace es únicamente cambiar el
estado (ver el método Set de FuzzyExpression)

01 public class FuzzyRule
02 {
03     private readonly FuzzySystem _engine;
04     private readonly Func _condition;
05     private Func _then;
06  
07  
08     public double Value { get; set; }
09  
10     public FuzzySystem Engine
11     {
12         get { return _engine; }
13     }
14  
15     public FuzzyRule(FuzzySystem engine, Func condition)
16     {
17         _engine = engine;
18         _condition = condition;
19     }
20  
21     public FuzzyExpression Eval()
22     {
23         Value = _condition(Engine).Value;
24  
25         if (Value > 0)
26         {
27             return _then(Engine);
28         }
29  
30         return null;
31     }
32  
33     public void Then(Func then)
34     {
35         _then = then;
36     }    
37 }


Defuzzyficando

Para evaluar nuestro sistema ..

 

01     for (int a = 0; a < 10; a++)
02     {
03         for (int b = 0; b < 10; b++)
04         {
05             _engine.Variables["A"].InputValue = a;
06             _engine.Variables["B"].InputValue = b;
07             _engine.Consecuent = "R";
08  
09             var r = _engine.Defuzzy();
10  
11             Console.WriteLine(string.Format("A {0} - B {1} = {2,-6} [{3,-10}][{4,-10}] = {5}",
12                 a,
13                 b,
14                 r,
15                 _engine.GetVariableState("A",a),
16                 _engine.GetVariableState("B",b),
17                 _engine.GetVariableState("R", r)));
18         }
19     }
20 }

Mejoras de diseño

Hay una cosa que no me gusta mucho y es el hecho de que mi clase
FuzzyExpression, tiene tanto el método Is como el método Set y esto
habría que dividirlo en dos para no llevar a errores a la hora de la
usar la interfaz fluida.

Errores de diseño

Pero no todo es de color de rosa, cuando planifique el sistema,
pensé en la posibilidad de guardar las expresiones lambda como texto y
después compilarlas usando CodeDom, bien tengo que deciros que cuando
me he puesto a ello me he llevado la desagradable sorpresa de que
CodeDom no soporta expresiones lambda, de modo que mi gozo en un pozo.

Y digo esto porque es de vital importancia el poder mantener las
reglas fuera del código, bien un archivo de configuración, un archivo
XML, lo que sea… de este modo podemos realizar ajustes sin tener que
recompilar todo …

Para una futura versión

Completar estas cositas …

5 comentarios en “Fuzzy Logic y expresiones lambda”

  1. Carlos,

    ¡Excelente post!

    Ideas que me rondan la cabeza:

    a) Sin temor alguno a equivocarme, creo que esto tiene todos los merecimientos para aparecer en alguna publicación impresa, como dotNetManía. ¿Por qué no lo amplías un poco y lo envías?

    b) A CodeDom, de todos modos, no le queda mucho tiempo como principal (o único) mecanismo para generar código “on the fly”. Te recomendaría echar un vistazo a las novedades de los árboles de expresiones en C# 4…

    Abrazo – Octavio

  2. El miércoles cenando que no se nos pase hablar largo y tendido sobre este tema… excelente post.

    Secundo la moción de Octavio… ¡artículo y papel!

  3. Hola, me ha gustado mucho tu post. Yo llevo un tiempo enredando con XNA y con logica difusa para acciones simples de IA.
    No tengo mucha idea de expresiones lambda, asi que puede que este preguntando una estupidez, pero ¿no seria posible definir reglas en XML como si fuesen nodos de un arbol de expresion (body, parameters, operation left, etc) y luego leer esa info, contruir un arbol de expresion y ejecutarlo? Si es posible, la cosa seria incluso mas potente, puesto que podrias definir nodos con expresiones simples y luego juntarlas en un arbol de expresion que sea una regla compleja formada por varias expresiones simples.
    Gracias por tu post.

  4. @Crowley

    Me alegro. Efectivamente ese es el cámino, al igual que apunta el gran Octavio, lo que pasa es que habría que hay que hacerlo con los árboles de expresiones del framework 4.0.

    En cuanto saque un poco de tiempo…. le doy una vuelta.

    carlos.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *