Saturday, September 4, 2010

Discrete Classification using F# and Fuzzy Logic

This post shows the first example based on the F# fuzzy logic reference module Fuzzy0. I continue with the theme of tomatoes. The example shows how fuzzy logic can be used to classify items such as tomatoes into discrete categories.

One of the nice things about the model used in Fuzzy0 is that it is so generic it can be adapted to anything. The input and fuzzification is simply a function, and the output and defuzzification is just another function. Several useful examples are included as part of Fuzzy0 both for the input function and the functionality connecting the input to the output, but many others are possible.

Previous examples have used as output a defuzzification method which produced a single number. This post shows something different. The output is a weight for each of several categories. Rather than combining these weights, they are kept discrete and a category is chosen based on the highest weight.

In the example below, the input uses the (fast becoming traditional) tomato diameter and color, while the output is a series of categories representing the highest-value use. The example is small and fairly straightforward, but some things merit pointing out:

1) The rule set uses “conjoin” to combine multiple input fuzzifications based on the minimum.

2) The tomato types are given a desirability factor. This is a dimensionless quantity representing all the things that go into deciding the value of a tomato: market price, cost of production, current supply, etc.

3) “inConst” is used to produce a constant value of 1.0 for ketchup, since all tomatoes are usable in some processed product such as ketchup.

4) The color is determined by a simple ratio similar to that you could get from a resistance bridge and a couple of photocells. Likewise, the size could be estimated using very simple sensor circuitry.

5) I don’t know a darn thing about the tomato industry.

If you're like me, one of the first questions that comes up when looking at a fuzzy logic system like this is: "why not do it some traditional way, such as a decision tree or a set of differential equations?" Here's one reason: with a few minutes of explanation, a domain expert such as a tomato buyer could look at the fuzzy logic rules and understand what is going on well enough to judge their quality and even update them. Try that with a set of differential equations; even most engineers wouldn't like to spend time doing it that way.

Without further ado, other than to add that, as always: this is presented "as-is" and without warranty or implied fitness of any kind; use at your own risk, here is the code:

open Fuzzy0
// Diameter of the tomato in inches.
let small  = inMin 1.0 2.0
let medium = inMid 1.0 2.0 3.0 4.0
let large  = inMax 3.0 4.0
// Color on a scale of
// If Red,Green are sensor values,
// could be computed by something like:
// color = (Red-Green)/(Red+Green+epsilon)
// ("epsilon" prevents division by zero.)
let green  = inMin -1.0 0.0
let yellow = inTri -1.0 0.0 1.0
let red    = inMax  0.0 1.0
/// Defines a tomato variety.
let variety label desirability  certainty =
  (label,desirability *certainty)
/// Rules to classify tomatoes.
// I just made these up.  
// In case it's still not obvious 
// by this blog post, I am clueless 
// about tomatoes.  
let tomatoRules = 
    (conjoin[medium;green], (variety "canning"     0.7))
    (conjoin[large; green], (variety "fryer"       0.6))
    (conjoin[small; yellow],(variety "decorative"  1.0))
    (conjoin[medium;yellow],(variety "sandwich"    0.5))
    (conjoin[small; red],   (variety "cherry"      0.8))
    (conjoin[medium;red],   (variety "salad"       0.9))
    (conjoin[large; red],   (variety "ripe"        0.7))
    (inConst(1.0),          (variety "ketchup"     0.1))
/// Classify a tomato.
/// Returns all non-zero assignments.
/// In actual use, one might use List.max 
/// to return a single value.
let classified = 
  fireAll tomatoRules [3.7;-0.5] // [diameter;color]
  |> List.filter (fun (_,c)->c>0.0)
  |> List.sortBy (fun (_,c)->1.0-c)
// Output using the values above is as follows.
// (Note: rounding assumes at least two significant
// digits throughout the process.)
//   fryer, 0.3
//   canning, 0.21
//   sandwich, 0.15
//   ketchup, 0.1
printfn "Your breakpoint here."

No comments: