Inference Engines: Forward and Backward Chaining
Chapter Sixty-Eight
Syllabus topic Module 2, "Inference engines"
Pages 237 to 240 of 378
In one line
An inference engine holds facts and rules, and it either pushes forward from the facts or works backward from a question.
In the wording you can write in an examination: an inference engine consists of a working memory of facts, a rule base of conditional rules, and a control strategy. Forward chaining, or data-driven inference, repeatedly fires every rule whose conditions are satisfied and adds its conclusion to working memory, until nothing new is derived. Backward chaining, or goal-driven inference, takes a goal, finds the rules that conclude it, and recursively attempts to establish their conditions.
The three parts
Working memory. The facts currently held. It grows during forward chaining and does not change during backward chaining.
Rule base. Conditional rules, each with conditions and a conclusion. They are data, not code, which is what makes a rule-based system modifiable without reprogramming.
Control strategy. Which direction to run, and, when several rules could fire at once, which to fire. That second question is conflict resolution and it is the same problem [Rule Precedence: The Four Principles] solves for Pāṇini.
The two directions
| Forward chaining | Backward chaining | |
|---|---|---|
| Starts from | the facts | a goal |
| Asks | what follows? | can this be shown? |
| Stops when | nothing new is derived | the goal is established or exhausted |
| Derives | everything derivable | only what the goal needs |
| Good when | there are few facts and many possible conclusions | there is one question and many facts |
| Natural for | monitoring, alerting, reacting to events | diagnosis, answering a query |
| Wasteful when | most conclusions are not wanted | most facts are relevant to the goal |
Both directions, on one rule base
"""Forward and backward chaining over one rule base, traced."""
RULES = [
("R1", ["smoky"], "fiery"),
("R2", ["fiery", "enclosed"], "hot"),
("R3", ["hot", "dry"], "fire-risk"),
("R4", ["wet"], "not-dry"),
]
FACTS = {"smoky", "enclosed", "dry"}
def forward(facts, rules):
known, trail = set(facts), []
changed = True
while changed:
changed = False
for name, conditions, conclusion in rules:
if conclusion in known:
continue
if all(c in known for c in conditions):
known.add(conclusion)
trail.append("%s fires: %s so %s" % (name, " and ".join(conditions), conclusion))
changed = True
return known, trail
def backward(goal, facts, rules, depth=0, trail=None, seen=None):
trail = trail if trail is not None else []
seen = seen or set()
pad = " " * depth
if goal in facts:
trail.append("%sgoal %s is a known fact" % (pad, goal))
return True, trail
if goal in seen:
trail.append("%sgoal %s is already being pursued: a circle" % (pad, goal))
return False, trail
applicable = [r for r in rules if r[2] == goal]
if not applicable:
trail.append("%sgoal %s: no rule concludes it, and it is not a fact" % (pad, goal))
return False, trail
for name, conditions, _ in applicable:
trail.append("%sgoal %s: try %s, which needs %s" % (pad, goal, name, " and ".join(conditions)))
if all(backward(c, facts, rules, depth + 1, trail, seen | {goal})[0] for c in conditions):
trail.append("%sgoal %s: established by %s" % (pad, goal, name))
return True, trail
trail.append("%sgoal %s: %s did not establish it" % (pad, goal, name))
return False, trail
print("facts:", ", ".join(sorted(FACTS)))
print("rules:")
for name, conds, concl in RULES:
print(" %-3s IF %-22s THEN %s" % (name, " and ".join(conds), concl))
print()
print("FORWARD chaining: derive everything derivable")
known, trail = forward(FACTS, RULES)
for line in trail:
print(" " + line)
print(" derived:", ", ".join(sorted(known - FACTS)) or "(nothing)")
print()
print("BACKWARD chaining: prove one goal, fire-risk")
ok, trail = backward("fire-risk", FACTS, RULES)
for line in trail:
print(" " + line)
print(" result:", "established" if ok else "not established")
print()
print("BACKWARD chaining on a goal that fails: not-dry")
ok, trail = backward("not-dry", FACTS, RULES)
for line in trail:
print(" " + line)
print(" result:", "established" if ok else "not established")
print()
print("how much work each does")
_, ftrail = forward(FACTS, RULES)
_, btrail = backward("fire-risk", FACTS, RULES)
print(" forward fired %d rule(s) and derived %d new fact(s)" % (len(ftrail), len(known - FACTS)))
print(" backward took %d step(s) to prove one goal" % len(btrail))Inference Engines: Forward and Backward Chaining
facts: dry, enclosed, smoky
rules:
R1 IF smoky THEN fiery
R2 IF fiery and enclosed THEN hot
R3 IF hot and dry THEN fire-risk
R4 IF wet THEN not-dry
FORWARD chaining: derive everything derivable
R1 fires: smoky so fiery
R2 fires: fiery and enclosed so hot
R3 fires: hot and dry so fire-risk
derived: fiery, fire-risk, hot
BACKWARD chaining: prove one goal, fire-risk
goal fire-risk: try R3, which needs hot and dry
goal hot: try R2, which needs fiery and enclosed
goal fiery: try R1, which needs smoky
goal smoky is a known fact
goal fiery: established by R1
goal enclosed is a known fact
goal hot: established by R2
goal dry is a known fact
goal fire-risk: established by R3
result: established
BACKWARD chaining on a goal that fails: not-dry
goal not-dry: try R4, which needs wet
goal wet: no rule concludes it, and it is not a fact
goal not-dry: R4 did not establish it
result: not established
how much work each does
forward fired 3 rule(s) and derived 3 new fact(s)
backward took 9 step(s) to prove one goalReading the two traces
Forward chaining fired three rules and derived three facts. It did not fire R4, because its condition was never satisfied. It stopped when a whole pass added nothing.
Backward chaining took nine steps to establish one goal. It went down from fire-risk to hot to fiery to smoky, hit a known fact, and came back up. The indentation is the structure of the proof, and reading it upward gives the derivation in the order a person would present it.
Inference Engines: Forward and Backward Chaining
And the failing goal is the useful one. Asking for not-dry finds R4, needs wet, finds no rule concluding wet and no such fact, and reports failure with the reason. An engine that says only "no" is unusable; one that says which subgoal it could not establish is a diagnostic tool.
Which direction to choose
The rule of thumb is about the ratio of facts to conclusions.
Choose forward chaining when the facts arrive and you want to know what they imply. A monitoring system receives events and must decide whether to alert. It has no goal in advance.
Choose backward chaining when there is a question. A diagnostic system is asked "is it the disk?" and should not derive everything derivable about the machine first.
And notice what the traces show about cost. Forward chaining derived three facts, all of which happened to be wanted. Add fifty rules whose conclusions nobody asked about and forward chaining derives all fifty; backward chaining touches none of them.
The Nyāya connection, stated exactly
Backward chaining is the shape of a Nyāya argument. You have a claim, you look for a vyāpti that would establish it, and you check whether the subject satisfies the vyāpti's condition. Goal, rule, subgoal.
And the trace is the five-member form, upside down. Read the backward trace from the innermost line outward and you get: this is smoky, whatever is smoky is fiery, so this is fiery, and so on up. [Nyāya Logic as an Inference Engine] builds an engine that emits exactly that, in the five members.
Forward chaining has no classical counterpart in Nyāya, because Nyāya is about establishing a claim somebody has made, not about generating claims. The nearest thing is Charaka's diagnostic procedure, which gathers evidence before forming a hypothesis, and even that is not the same.
Conflict resolution, and the connection to Module I
When two rules could fire at once a forward-chaining engine must choose. The classical strategies are these.
Specificity. Fire the rule with more conditions, that is, the more specific one. That is apavāda, from [Rule Precedence: The Four Principles].
Recency. Fire the rule whose triggering facts were added most recently.
Order. Fire the rule that appears first, or last, in the rule base. That is paratva, sūtra 1.4.2.
Refraction. Do not fire the same rule on the same facts twice, which is what the if conclusion in known: continue line in the listing achieves, and it is what stops the loop.
Two of the four strategies are Pāṇini's, and an answer that says so has made a real cross-module connection.
Inference Engines: Forward and Backward Chaining
What an inference engine is NOT
It is not a theorem prover. It applies rules; it does not search for proofs in a logic with quantifiers, and the rules here have no variables at all.
It is not guaranteed to terminate. This one does, because refraction prevents a rule firing twice on the same facts and the set of derivable facts is finite. A rule base that can generate new terms need not terminate.
It does not handle uncertainty. Every fact is present or absent and every rule fires or does not. Real expert systems attach certainty factors, and [Expert Systems, and MYCIN as the Comparison] records what that cost.
It does not decide what the rules should be. Getting the rules out of an expert's head is knowledge acquisition, and it is the hard part.
Quick revision
- Three parts: working memory, rule base, control strategy.
- Forward chaining: from facts, derive everything, stop when a pass adds nothing. Good for monitoring.
- Backward chaining: from a goal, find rules concluding it, recurse on their conditions. Good for diagnosis.
- The backward trace, read outward, is a proof; a failing goal should report which subgoal failed.
- Conflict resolution strategies: specificity, recency, order, refraction. Specificity is apavāda and order is paratva.
- Refraction is what prevents the loop.
- Not a theorem prover, not guaranteed to terminate in general, and no notion of uncertainty.
Test yourself
1. Distinguish forward from backward chaining by what each starts from and what each derives.
Forward chaining starts from the facts and derives everything derivable, stopping when a whole pass adds nothing. Backward chaining starts from a goal and derives only what that goal needs, stopping when the goal is established or the possibilities are exhausted.
2. Which would you use for an alerting system, and which for a diagnostic tool?
Forward chaining for alerting, because events arrive and there is no goal in advance. Backward chaining for diagnosis, because there is a question and deriving everything about the system first would be wasteful.
3. Why must a failing backward-chaining query report more than "no"?
Because the useful information is which subgoal could not be established. An engine that reports only failure gives a user nothing to act on; one that names the missing fact or the missing rule is a diagnostic tool.
4. Name two conflict resolution strategies that correspond to Pāṇini's precedence principles.
Specificity, firing the rule with the narrower conditions, corresponds to apavāda. Rule order, firing by position in the rule base, corresponds to paratva, sūtra 1.4.2.
The rest of this subject
These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.