Fuzzy Inference: Teaching Machines to Think in Shades of Grey
Facts and Degrees
In classical logic, something is true or false. The cat is on the mat, or it is not. A patient has a fever, or they do not. There is no middle ground.
Fuzzy logic adds a dial.
Instead of true/false, every statement carries a degree of belief – a number between 0 and 1. A degree of 1.0 means certainty. A degree of 0.0 means we have no belief at all. And everything in between is fair game.
Here is the simplest possible fuzzy fact:
# A fuzzy fact: "Rex has hair" with 85% confidence
engine.add_fact("has-hair", ["rex"], 0.85)
The predicate is has-hair. The argument is rex. The degree is 0.85. Maybe we observed Rex from a distance, or the photo was blurry. We are fairly sure Rex has hair, but not certain.
This is the building block of everything that follows. A fuzzy knowledge base is just a collection of these facts, each with its own degree. Some facts we are sure about (deg=1.0). Others are tentative guesses (deg=0.3). The engine treats them all the same way – it just pays attention to the number.
One important detail: when two sources assert the same fact with different degrees, the engine keeps the higher one. This is called fuzzy-OR. If one sensor says has-hair(rex) at 0.85 and another says it at 0.92, the engine stores 0.92. Optimistic, but reasonable – the stronger evidence wins.
engine.add_fact("has-hair", ["rex"], 0.85)
engine.add_fact("has-hair", ["rex"], 0.92) # fuzzy-OR: keeps 0.92
In the widget below, you can create fuzzy facts and drag the degree slider to see how the degree changes the visual representation. A fact at 1.0 is solid and bright. A fact at 0.1 is faded, barely there. This is not just decoration – it is the engine’s uncertainty, made visible.
Rules
Facts alone are inert. To reason, we need rules – if-then statements that produce new facts from existing ones.
A fuzzy rule looks like this: “If X has hair, then X is a mammal.” In code:
engine.add_rule(
name="mammal-rule",
conditions=[{"pred": "has-hair", "args": ["?x"], "degVar": "?d"}],
actions=[{
"type": "add",
"fact": {"pred": "is-mammal", "args": ["?x"], "deg": ["*", 0.95, "?d"]}
}],
priority=60,
)
There is a lot going on here, so let us unpack it.
Pattern variables. The ?x in the condition is a variable. It matches any argument. When the engine finds has-hair(rex, 0.85), it binds ?x to rex. The same ?x then appears in the action, so the engine adds is-mammal(rex, ...).
