Remember: Everything in AI is either representation or search
Logical formulas are often a very convenient representation
Why convenient?
Logic is used extensively in AI!
a∧b
: a and b
a∨b
: a or b (or both!)
¬a
: not a
a→b
: a implies b, which means: if a
is true, b
also has to be true (but if a
is false, b
can be true or false). Logically equivalent to ¬a∨b
When you see iff (or in Spanish "sii", "ssi" or "syss") in text it is not a typo, it means "if and only if", a bidirectional implication ("a iff b" means that if a
is true b
is true, but if a
is false, b
is also false)
How do we tell if a formula is true?
We need to know if a and b (and all other constants) are "true"
In other words, if we have some "interpretation" W
of a
and b
, we can determine truth values
W⊨a∧b
iff a
and b
are both true in W
, i.e. W⊨a
and W⊨b
So what is W?
W is an interpretation (also called state or world), which assigns truth values to every constant
Sometimes it is convenient to just represent W as a set that contains everything that is true
Conversely, everything that is not in the set is false ("closed world assumption")
A model is an interpretation under which a formula is satisfied
We say W models a formula, if the formula is satisfied under that interpretation
A formula is a tautology if all interpretations are models
W={a,b,c}
W⊨a∧bW⊨a∨dW⊭
Representing any non-trivial state in propositional logic is tedious (imagine chess: there has to be one variable for each possible location of each piece)
Predicate Logic, on the other hand, represents the world as objects and relations between them
Objects: constants
Relations: Sets of n-tuples of objects, called predicates (n is the arity of the predicate)
(Functors: Assignments of n-tuples of objects to objects)
We also have quantifiers \forall, \exists
\mathit{human}(\mathit{socrates})\\ \forall h: \mathit{human}(h) \rightarrow \mathit{mortal}(h)\\ \mathit{friends}(\mathit{socrates}, \mathit{plato})
\mathit{human}(\mathit{socrates})\\
\forall h: \mathit{human}(h) \rightarrow \mathit{mortal}(h)\\
\mathit{friends}(\mathit{socrates}, \mathit{plato})
\mathit{human}(h)
is an unary relation, i.e. a set of 1-tuples/single elements. It is often more convenient to write:
\forall h \in \mathit{human}: \mathit{mortal}(h)
\mathit{human}(\mathit{socrates})\\
\forall h: \mathit{human}(h) \rightarrow \mathit{mortal}(h)\\
\mathit{friends}(\mathit{socrates}, \mathit{plato})
\mathit{human}(h)
is an unary relation, i.e. a set of 1-tuples/single elements. It is often more convenient to write:
\forall h \in \mathit{human}: \mathit{mortal}(h)
Using set-operators, we could also write (\mathit{socrates}, \mathit{plato}) \in \mathit{friends}
In fact, as we will see, that's how our interpretations work.
A predicate name with parameters is called an Atom or atomic formula, e.g. \mathit{human}(h)
A literal is an atom of the negation of an atom, e.g. \neg \mathit{human}(h)
The h
can either be a constant or a variable
If h
is part of our domain, it is a constant
If h
does not refer to any concrete object, it is a variable. A variable can be bound by a quantifier, or it can be unbound/free
A formula with no free variables is called ground
Given two predicate logic formulas \phi
and \psi
, we can use the usual logical connectives \wedge, \vee, \rightarrow, \neg
to construct more complex formulas
Conceptually, this will result in a (syntax) tree, where each interior node is an operator, and the leaves are atoms
If we have a formula \phi
that contains a free variable x
, we can substitute that variable with a value t, written \phi[t/x]
Given a (ground) formula \phi
and an interpretation/world W
, we want to know if the formula holds in that world, i.e. if the world is a model for the formula
We write this as W \models \phi
("W models phi")
How do we determine that?
\phi
is an atom, it is true iff \phi \in W
\phi
is a more complex formula, use the syntax tree to determine the truth valueW \models \neg \phi \:\:\text{iff}\:\:W \not\models \phi\\ W \models \phi \wedge \psi\:\:\text{iff}\:\:W \models \phi\:\:\text{and}\:\:W \models \psi\\ W \models \phi \vee \psi\:\:\text{iff}\:\:W \models \phi\:\:\text{or}\:\:W \models \psi\\ W \models \phi \rightarrow \psi\:\:\text{iff}\:\:W \not\models \phi\:\:\text{or}\:\:W \models \psi\\ W \models \forall x \in X: \phi\:\:\text{iff}\:\:W \models \phi[t/x]\:\:\text{for all}\:t \in X\\ W \models \exists x \in X: \phi\:\:\text{iff}\:\:W \models \phi[t/x]\:\:\text{for any}\:t \in X
We start with a domain, the set of all objects we can talk about
Technically, an interpretation has to assign objects to all our constants, since Socrates and Plato could be the same person in an interpretation! We will use the unique-names assumption to avoid such problems and will just assume that the constants are the same as the objects.
Then, the interpretation has to contain definitions for every predicate, in the form of a set of tuples.
The closed world assumption states that everything we don't know to be true (i.e. everything not in the set representing a predicate) is in fact false.
Using these assumptions, we can represent our interpretation as a set of true atoms, or as a collection of sets, one for each predicate.
W: \mathit{human} = \{\mathit{socrates}, \mathit{plato}, \mathit{aristotle}\}\\ \mathit{mortal} = \{\mathit{socrates}, \mathit{plato}, \mathit{aristotle}, \mathit{DonaldDuck}\}\\ \mathit{friends} = \{(\mathit{socrates}, \mathit{plato}), (\mathit{socrates}, \mathit{DonaldDuck})\}\\
W \models \mathit{human}(\mathit{socrates}) \\ W \models \forall h: \mathit{human}(h) \rightarrow \mathit{mortal}(h) \\ W \models \mathit{friends}(\mathit{socrates}, \mathit{plato})\\ W \not\models \mathit{friends}(\mathit{socrates}, \mathit{socrates})\\ \ldots
W = \{\mathit{human}(\mathit{socrates}), \mathit{human}(\mathit{plato}), \mathit{human}(\mathit{aristotle}), \\ \mathit{mortal}(\mathit{socrates}), \mathit{mortal}(\mathit{plato}), \mathit{mortal}(\mathit{aristotle}), \mathit{mortal}(\mathit{DonaldDuck}),\\ \mathit{friends}(\mathit{socrates}, \mathit{plato}), \mathit{friends}(\mathit{socrates}, \mathit{DonaldDuck})\}\\
W \models \mathit{human}(\mathit{socrates}) \\ W \models \forall h: \mathit{human}(h) \rightarrow \mathit{mortal}(h) \\ W \models \mathit{friends}(\mathit{socrates}, \mathit{plato})\\ W \not\models \mathit{friends}(\mathit{socrates}, \mathit{socrates})\\ \ldots
If we actually want to use logic for something, we need an implementation
We will start with how to store formulas and interpretations in data structures
This will provide us with a way to query the truth-values of logical formulas under an interpretation
Later we will see how we can manipulate these interpretations
First we need to represent atoms, maybe just as tuples: human(h)
becomes ("human", "h")
or ("human", ("h",))
Then we can store the atoms in a suitable data structure, e.g. a python set, to get our world
We represent our syntax tree using a data structure, e.g.
class LogicalFormula: def isModeledBy(self, world): return False
We subclass this for each syntactic element, overriding isModeledBy
accordingly
For example, the subclass Atom
, will do return self.atom in world.atoms
(and store self.atom
appropriately)
The subclass And
needs to call isModeledBy
for all its children, and return True
iff all of them return True
class And(LogicalFormula): def __init__(self, children): self.children = children def isModeledBy(self, world): result = True for c in self.children: result = result and c.isModeledBy(world) return result
Note: This and can have more than two children, which is a useful optimization!
\forall
and \exists
we need to be able to substitute variables in formulas, so we need to add another method to our base class:def substitute(self, variable, value): return self
This method should replace the given variable with the given value in all children, or parameters (in the case of atoms), and returns a copy of itself with the substitution performed
Forall
can then use this in its isModeledBy
method
def isModeledBy(self, world): values = world.getValues(self.variable) for value in values: if not isModeledBy(self.child.substitute(self.variable, value): return False return True
Now we can represent logical interpretations and formulas
We can use this to determine which formulas hold under our interpretation
An agent could, for example, use this to collect some true data and then reason about which rules hold (e.g. collect information about all animals and items in a zoo, and then determine if every animal has something to eat)
The other direction is even more interesting, though: Collect some information and have the rules, and "generate" more information
Just having a static interpretation of the world is not very interesting
One form of intelligence could be to generate/derive "new" knowledge
Or answer questions
This process is called inference
Facts: What we know about the world, which includes literals and formulas
Rules: Logical instructions that tell us how we can generate more facts, using properties of logic
You can also imagine this as a logical proof: We know some things about the world, and we prove that some other things logically follow
Maybe the most "famous" inference rule is the Modus Ponens:
{a, a \rightarrow b} \models b
If we know a
and we know that a
implies b
, then we can conclude b
Note how the notation is a generalization of what we used for interpretations: Any set can be on the left side!
With this, an agent can start generating facts that exist and fill a database
A user can then ask a "question" in the form of a query: Is x true?
If the agent has the asked fact in their database they answer with yes
By keeping track of the used rules, the agent can also provide a proof!
In practice, we may have "many" possible facts
Imagine a logical reasoning system for natural numbers: We can say a number is prime if it has some properties (number of proper divisors is equal to two), but we don't want the agent to generate all prime numbers
Instead we can answer queries: Does a particular formula hold, e.g. "Is 9 a prime number"?
There are several different algorithms to answer such queries
Resolution is one such algorithm to perform inference
The idea is to take disjunctions ("or"s) of logical literals as rules
If we know that one of this literals is false, we can infer that at least one of the others must be true
Also remember that an implication is also an or!
a \rightarrow b \Leftrightarrow \neg a \vee b\\ {\neg a \vee b, a} \models b
How to do queries: We negate the query and try to "resolve" it with what we know
{\neg a \vee b, a, \neg b} \models \bot
Prolog is a logical programming language that allows us to write such disjunctions and then does the inference by resolution for us!
a.b :- a.c :- not(b).?- b true?- cfalse
Unfortunately we don't have time to go into detail :(
We often want to represent a world that is changing, rather than just static facts
Simple solution: Let's use constants representing (discrete) times, and increase the arity of each predicate by one to account for time
For example, \mathit{human}(\mathit{socrates})
becomes \mathit{human}(\mathit{socrates}, 0)
, or
\forall t \in T: \mathit{human}(\mathit{socrates}, t)
An action is something that changes some truth values over time, often represented as an implication, like \mathit{alive}(\mathit{socrates}, 400 \text{BCE}) \rightarrow \neg \mathit{alive}(\mathit{socrates}, 399 \text{BCE})
This can be generalized with quantifiers and special predicates that indicate when actions occur, if needed
Consider Fred, a turkey, and a gun
At time 0, Fred is alive and the gun is unloaded
We load the gun, so that at time 1 the gun is loaded
Then we wait
At time 2 we shoot, such that at time 3 Fred is dead
\mathit{alive}(\mathit{Fred}, 0)\\ \neg \mathit{loaded}(\mathit{Gun}, 0)\\ \neg \mathit{loaded}(\mathit{Gun}, 0) \rightarrow \mathit{loaded}(\mathit{Gun}, 1)\\ \mathit{loaded}(\mathit{Gun}, 2) \rightarrow \neg \mathit{alive}(\mathit{Fred}, 3)
\mathit{alive}(\mathit{Fred}, 0)\\ \neg \mathit{loaded}(\mathit{Gun}, 0)\\ \neg \mathit{loaded}(\mathit{Gun}, 0) \rightarrow \mathit{loaded}(\mathit{Gun}, 1)\\ \mathit{loaded}(\mathit{Gun}, 2) \rightarrow \neg \mathit{alive}(\mathit{Fred}, 3)
What about \mathit{alive}(\mathit{Fred}, 1)
?
What about \mathit{loaded}(\mathit{Gun}, 2)
?
Idea: Let's say "the least" number of things change each time step!
\mathit{alive}(\mathit{Fred}, 0)\\ \neg \mathit{loaded}(\mathit{Gun}, 0)\\ \neg \mathit{loaded}(\mathit{Gun}, 0) \rightarrow \mathit{loaded}(\mathit{Gun}, 1)\\ \mathit{loaded}(\mathit{Gun}, 2) \rightarrow \neg \mathit{alive}(\mathit{Fred}, 3)
What about \mathit{alive}(\mathit{Fred}, 1)
?
What about \mathit{loaded}(\mathit{Gun}, 2)
?
Idea: Let's say "the least" number of things change each time step!
The turkey could still die at time step 1 and stay dead ...
The Yale Shooting Problem is an illustration of the Frame Problem: Everything that is not changed by an action (the "frame") should stay the same
But how would we even write this in logical formulas?
There are several solutions!
One approach: Frame axioms. For each action state what the action leaves unchanged. This may require a lot of extra formulas.
Another approach: Distinguish states and actions, and represent changes as a transition system
We start with an initial state s_0 = \{\mathit{alive}(\mathit{Fred}), \neg \mathit{loaded}(\mathit{Gun}) \}
(or, using the closed-world assumption simply s_0 = \{\mathit{alive}(\mathit{Fred})\}
), which we can use for logical queries such as
s_0 \models \mathit{alive}(\mathit{Fred})
We have an action load(Gun)
which turns a state into another state:
s_1 = \{\mathit{alive}(\mathit{Fred}), \mathit{loaded}(\mathit{Gun}) \}
Now we can query s_1 \models \mathit{alive}(\mathit{Fred})
This forms the basis for the planning problem, which we will discuss for the rest of the semester
We can express many interesting problems this way, such as how to distribute packages using trucks and airplanes, determine a robot's actions to perform a task, play games, etc.
However, it also has drawbacks: If our states only represent individual time steps, we can't (easily) query things like "how long was Fred alive"
As another part of task 1 you will implement a way to apply actions to worlds/states
We consider our actions to consist of two parts: a precondition and an effect
The precondition tells us when we can use an action (e.g. we can only shoot if we even have the gun)
The effect tells us what happens (if the gun is loaded, we kill the turkey, otherwise nothing happens)
We can determine if a precondition holds in a world with our models
function
We also need to be able to apply an effect, which is where apply
comes in
For simplicity, we will assume our effects are also represented as a logical formula, which tells us what is true after the action
However, we will only support a subset of formulas as possible effects
For example, what would a formula like a \vee b
mean as an effect?
For simplicity, we will assume our effects are also represented as a logical formula, which tells us what is true after the action
However, we will only support a subset of formulas as possible effects
For example, what would a formula like a \vee b
mean as an effect?
We'll call the subset of formulas we allow the effect formulas
An effect formula can be a conjunction of:
when
expressionsdef apply(self, world): return world
Depending on the formula, we can then override this method and apply the changes of the subformulas
This may be tricky to get right (particularly negated literals), I'll show you another way later
For And
you can do something like:
def apply(self, world): result = world for c in self.children: result = c.apply(result) return result
Some actions have different effects, depending on what is already true in the world, like firing the gun which as a different effect when the gun is loaded
We could make two actions: fire-gun-if-loaded
and fire-gun-if-not-loaded
, but this becomes cumbersome to write
Instead we allow conditional effects that are only applied when a condition holds:
\operatorname{when}\:(\phi)\:(\psi)
Say we have the worlds
s_0 = \{\mathit{alive}(\mathit{Fred})\}\\ s_1 = \{ \mathit{alive}(\mathit{Fred}), \mathit{loaded}(\mathit{Gun}) \}
And the effect of the shoot action:
e = \operatorname{when}\:(\mathit{loaded}(\mathit{Gun}))\:(\neg \mathit{alive}(\mathit{Fred}) \wedge \neg \mathit{loaded}(\mathit{Gun})
What happens when we apply e to s_0
?
What happens when we apply it to s_1
?
We can represent a when-expression as a tuple in the Abstract Syntax Tree ("when", x, y)
, where x is the condition and y is the conditional effect
Just like with the logical connectives, we introduce a subclass of LogicalFormula
that represents a when-expression
When we apply the when-expression to a world, we apply the y-part iff the x-part holds, e.g.
class When(LogicalFormula): def apply(self, world): if self.condition.isModeledBy(world): return self.effect.apply(world) return world
There are several ways to implement action application
One is with the apply
method in all LogicalFormula
subclasses
Another is to determine the additions and deletions a formula causes and use set operations on the set of atoms
class LogicalFormula: def getChanges(self, world): return (self.getAdditions(world),self.getDeletions(world))
Keyboard shortcuts
↑, ←, Pg Up, k | Go to previous slide |
↓, →, Pg Dn, Space, j | Go to next slide |
Home | Go to first slide |
End | Go to last slide |
Number + Return | Go to specific slide |
b / m / f | Toggle blackout / mirrored / fullscreen mode |
c | Clone slideshow |
p | Toggle presenter mode |
t | Restart the presentation timer |
?, h | Toggle this help |
Esc | Back to slideshow |