Semantic networks, inheritance, and default reasoning
Represent categories and properties as a network, and let specific knowledge override general defaults.
parents = {"tweety": "penguin", "penguin": "bird", "bird": "animal", "polly": "parrot", "parrot": "bird"}
properties = {
"animal": {"alive": True},
"bird": {"flies": True, "legs": 2},
"penguin": {"flies": False},
}
def lookup(thing, attribute):
while thing is not None:
if attribute in properties.get(thing, {}):
return properties[thing][attribute]
thing = parents.get(thing)
return None
print(lookup("polly", "flies"))
print(lookup("tweety", "flies"))
print(lookup("tweety", "legs"), lookup("tweety", "alive"))
print(lookup("tweety", "color"))True False 2 True None
Explanation
Not all knowledge is best written as logical formulas. A semantic network represents concepts as nodes and relations as labelled links, with is-a links linking an individual or category to a more general one. Frames add structured slots for each concept. These structures make it easy to inherit properties: a bird has two legs, so a parrot, being a bird, has two legs too, without that fact being stored on the parrot.
Inheritance supports default reasoning. 'Birds fly' is a useful rule even though penguins do not. The lookup walks up the is-a chain and returns the first value it finds, so the most specific definition wins: a penguin's flies = False overrides the bird default. This kind of reasoning is nonmonotonic, because learning that Tweety is a penguin withdraws the earlier conclusion that Tweety flies. Classical logic is monotonic: adding a sentence can never remove a conclusion.
Pure single-inheritance trees are easy to read. With multiple inheritance a node may have several parents that disagree, as in the Nixon diamond, so the system needs a rule to resolve or report the conflict. Modern descendants of semantic networks include RDF knowledge graphs, description logics, and the OWL ontology language, which add precise logical meaning and automated reasoning to the same intuitive structure.
Explore the dry run
parents = {"tweety": "penguin", "penguin": "bird", "bird": "animal", "polly": "parrot", "parrot": "bird"}properties = { "animal": {"alive": True}, "bird": {"flies": True, "legs": 2}, "penguin": {"flies": False},} def lookup(thing, attribute): while thing is not None: if attribute in properties.get(thing, {}): return properties[thing][attribute] thing = parents.get(thing) return None print(lookup("polly", "flies"))print(lookup("tweety", "flies"))print(lookup("tweety", "legs"), lookup("tweety", "alive"))print(lookup("tweety", "color"))Follow the example
Start to inspect the source, values, and output. These prepared steps explain the published sample; grouped loop steps describe multiple iterations.
View the complete dry-run table
| Step / line | What happens | State after step | Output so far |
|---|---|---|---|
| If the property is not defined here, follow the is-a link to the parent concept and look again. | — | — | |
| Polly is a parrot, and a parrot is a bird. The bird default says flies = True, so it is inherited. | — | True | |
| Tweety is a penguin. penguin defines flies = False, which is found before the bird default and overrides it. | — | True False | |
| legs comes from bird and alive from animal, two and three steps up the chain. | — | True False 2 True | |
| Nothing in the chain defines color, so the answer is unknown: None. | — | True False 2 True None |
Cost and common mistakes
A lookup follows at most the depth of the hierarchy, so it is O(d) for depth d.
- None means unknown, not false. The closed-world assumption (what is not known is false) is a modelling choice with consequences.
- Default conclusions can be retracted. Do not treat an inherited default as certain knowledge.
- With multiple inheritance, a lookup that returns the first path found may hide a conflict. Decide how conflicts are reported or resolved.
Try it yourself
How would you record that 'opus' is a penguin who can fly in a cartoon?
Reveal answer and reasoning
Give opus its own definition: parents["opus"] = "penguin" and properties["opus"] = {"flies": True}. The most specific definition overrides penguin's.