Saturday, August 28, 2010

Conjunctive Fuzzy Logic Rules in F#

Today’s installment is just a small change from yesterday’s. It shows how to make multipart conjunctive rules by storing the input sets in a list and using the “min” operator to combine the results into a truncation height. To do this, it adds vector versions of the fire and fire all functions.

As a test, I continue the tomato theme, this time adding a second set of fuzzy sets to indicate whether the tomatoes are green or red. Green tomatoes, in Neil’s tomato world, are generally only held to be superior to red tomatoes when they are small frying tomatoes. I ran some test numbers, and the graph is shown below:

So what good is this? Would anyone ever build such a system? Probably not; I’m sure there are better ways to sort tomatoes. But it illustrates one of the general classes of problems for which fuzzy logic is appropriate. So just for fun, let’s imagine how one might us such a system in the real world:

Neil is a tomato buyer for a local food co-op. He buys tomatoes from a large number of small growers, all of whom produce crops of mixed tomatoes, which are harvested sporadically throughout the growing season. The problem is: how to make sure each buyer gets a fair proportion of the return without increasing the overhead too much? Neil notices a bunch of spare parts in his workshop that may help. Using these, he quickly rigs up a small, simple conveyor system – small enough to fit on a pickup truck – for grading the tomatoes. Each tomato rolls over a thin window in the conveyor, where the diminution of light is used to estimate the size of the tomato. It also rolls past two phototransistors, one with a green filter and one with a red, which estimate its color. This information is fed to Neil’s laptop, where the fuzzy logic system defined below is used to estimate the value of the crop. Since the rules are so simple, every couple of weeks, based on discussions with the co-op, the fuzzy logic rules are adjusted to account for the current price of tomatoes. So there it is: all that tomato wisdom, distilled by fuzzy logic into a ketchup of happy farmers, grocers, and customers!

And below is the code. (As always, all the code here is presented "as-is" and without warranty or implied fitness of any kind; use at your own risk.)

module Fuzzy
// The input functions are trapezoids.
// The precomputations and closures
// make them look more complicated than
// they really are.
// Technically, since Min and Max are
// trapezoids with one side at infinity,
// a single function would suffice.
// But three functions are more efficient
// and comprehensible.
// Infinite to the left.
let inMin x0 x1 =
  let m = 1.0/(x0-x1)
  let b = 1.0-x0*m 
  (fun x ->  
     match x<=x0 with
     | true -> 1.0
     | _ -> 
     match x<=x1 with
     | true -> x*m+b
     | _ -> 0.0 )
// In the middle.
let inMid x0 x1 x2 x3 =
  let ml = 1.0/(x1-x0)
  let mh = 1.0/(x2-x3)
  let bl = 1.0-x1*ml
  let bh = 1.0-x2*mh
  (fun x -> 
     match x<x0 with
     | true -> 0.0
     | _ ->
     match x<x1 with
     | true -> x*ml+bl
     | _ ->
     match x<=x2 with
     | true -> 1.0
     | _ ->
     match x<=x3 with
     | true -> x*mh+bh
     | _ -> 0.0 )
// Infinite to the right.
let inMax x0 x1 =
  let m = 1.0/(x1-x0)
  let b = 1.0-x1*m 
  (fun x ->  
     match x>=x1 with
     | true -> 1.0
     | _ -> 
     match x>=x0 with
     | true -> x*m+b
     | _ -> 0.0 )
// The output set is a symmetric triangle.
// This function returns the area and centroid.
let outSym xc dx h = 
// Fire one rule.
let fire x (inSet,outSet) =
  x |> inSet |> outSet
// Fire a rule vector.
let fireV xl (inSets,outSet) =
  Seq.map2 (fun f x->f x) inSets xl 
  |> Seq.min
  |> outSet 
// Fire and defuzzify a ruleset.
let private fireAll0 f sets x = (f x) sets 
  |> List.fold (fun (aa,cc)(a,c)->(aa+a,cc+a*c)) (0.0,0.0)
  |> (fun (aa,cc)->cc/aa)
// Scalar fire and defuzzify a ruleset.
let fireAll sets x = 
  fireAll0 fire sets x
// Vector fire and defuzzify a ruleset.
let fireAllV sets x = 
  fireAll0 fireV sets x
// Here's the now familiar tomato problem.
// By happy coincidence, all the input sets 
// can be coded as trapezoids and the output
// sets as symmetric triangles.
let tiny   = inMin 1.0 2.0 
let small  = inMid 1.0 2.0 2.0 3.0
let medium = inMid 2.0 3.0 3.0 4.0
let large  = inMax 3.0 5.0
let green  = inMin 0.0 1.0 
let red    = inMax 0.0 1.0 
let isGreen = 0.0
let isRed   = 1.0
let cheap      = outSym 1.00 0.50
let moderate   = outSym 1.50 0.50
let expensive  = outSym 2.00 0.50
let outrageous = outSym 4.00 2.00
let rules =
// Inspection will show that the results
// are identical to the "tzoid" version.
for size in 0.25..0.25..5.00 do
  printfn "%f, %f, %f" 
    (fireAllV rules [size;isGreen])
    (fireAllV rules [size;isRed])
printf "Your breakpoint here."

No comments: