class: center, middle # Artificial Intelligence ### Logic --- class: center, middle # Logic --- # Why Logic? * Remember: Everything in AI is either representation or search * Logical formulas are often a very convenient representation * Why convenient? - Can be generated automatically - Can be processed automatically - Are very expressive * Logic is used extensively in AI! --- # Propositional Logic * `\(a \wedge b\)`: a **and** b * `\(a \vee b\)`: a **or** b (or both!) * `\(\neg a\)`: **not** a * `\(a \rightarrow 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 `\(\neg a \vee 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) --- # Propositional Logic * 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 \models a \wedge b \)` iff `a` and `b` are both true in `W`, i.e. `\( W \models a \)` **and** `\(W \models b \)` * So what is W? --- class: medium # Interpretations * 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 --- # Interpretations $$ W = \\{a, b, c\\} $$ $$ W \models a \wedge b \\\\ W \models a \vee d \\\\ W \not\models a \wedge d\\\\ W \models d \rightarrow a\\\\ \ldots $$ --- class: center, middle # Predicate Logic --- # Predicate Logic * 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 \)` --- # Predicate Logic: Example $$ \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. --- # Predicate Logic: Syntax * 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* --- # Predicate Logic: Syntax * 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]\)` --- # Predicate Logic: Semantics * 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? - If `\(\phi\)` is an atom, it is true iff `\(\phi \in W\)` - If `\(\phi\)` is a more complex formula, use the syntax tree to determine the truth value --- # Predicate Logic: Semantics $$ W \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 $$ --- class: small # Predicate Logic: Interpretations * 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. --- # Predicate Logic: Interpretations 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 $$ --- # Predicate Logic: Interpretations $$ 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 $$ --- class: center, middle # Data Structures --- # Data Structures * 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 --- class: small # Data Structures * 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` --- # Data Structure: And ``` 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! --- class: small # Data Structures * For the quantifiers `\(\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 ``` --- # Data Structures * 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 --- class: center, middle # Inference --- # Inference * 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* --- # 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 --- class: mediumt # Modus Ponens 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! --- class: medium # Queries * 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! --- # Inference Algorithms * 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 * 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 $$ --- class: mediumt # Prolog Prolog is a logical programming language that allows us to write such disjunctions and then does the inference by resolution for us! ```Prolog a. b :- a. c :- not(b). ?- b true ?- c false ``` Unfortunately we don't have time to go into detail :( --- class: center, middle # Actions --- # Actions? * 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 --- # The Yale Shooting Problem * 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 --- # The Yale Shooting Problem $$ \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 Frame Problem - 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 --- class: center, middle # Transition Systems --- # Transition System Approach - 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: - If the Gun is not loaded, it becomes loaded - If the Gun is already loaded, nothing happens - `\(s_1 = \{\mathit{alive}(\mathit{Fred}), \mathit{loaded}(\mathit{Gun}) \}\)` - Now we can query `\(s_1 \models \mathit{alive}(\mathit{Fred})\)` --- # Transition System Approach - 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 --- # What is an action? * 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 --- class: medium # What is 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: - Literals (atoms and negated atoms) - Forall-quantifiers where the inner formula is also an effect formula - `when` expressions --- # Effect Application * Since an effect is just a formula, we could add a method to our base class: ``` def 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 ``` --- # When-Expressions * 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) $$ * Note that the condition can be *any* logical formula, but the conditional effect has to be an effect formula --- # When-Expressions Example 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\)`? --- # When-Expressions, Implementation * 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 ``` --- class: medium # Implementing Action Application * 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)) ``` * The latter is probably (depending on the exact implementation, and the rest of the code) slightly faster, because it makes fewer copies of the atom set, and you could precompute a lot (minus conditional expressions) --- # References - [Logic and AI](https://plato.stanford.edu/entries/logic-ai/) - [Online Prolog Interpreter](https://swish.swi-prolog.org/) - [The Frame Problem](https://plato.stanford.edu/entries/frame-problem/)