[MUSIC PLAYING] BRIAN YU: All right. Welcome back, everyone, to an
introduction to artificial intelligence with Python. Last time we took a
look at search problems in particular, where
we have AI agents that are trying to solve some sort
of problem by taking actions in some sort of environment,
whether that environment is trying to take actions
by playing moves in a game, or whether those actions are something
like trying to figure out where to make turns in order to get driving
directions from point A to point B. This time we're going to turn
our attention more generally to just this idea of knowledge. The idea that a lot of intelligence
is based on knowledge, especially if we think about human intelligence. People know information. We know facts about the world. And using that information
that we know, we're able to draw conclusions--
reason about the information that we know in order to figure out
how to do something or figure out some other piece of information that
we conclude based on the information we already have available to us. What we'd like to focus on now is the
ability to take this idea of knowledge and being able to reason
based on knowledge, and apply those ideas to
artificial intelligence. In particular, we're
going to be building what are known as knowledge-based agents. Agents that are able to reason and act
by representing knowledge internally. Somehow inside of our AI,
they have some understanding of what it means to know something. And ideally, they have some
algorithms, or some techniques they can use based on that
knowledge that they know in order to figure out the solution
to a problem, or figure out some additional piece of information
that can be helpful in some sense. So what do we mean by
reasoning based on knowledge to be able to draw conclusions? Well, let's look at a simple example
drawn from the world of Harry Potter. We take one sentence
that we know to be true. If it didn't rain, then
Harry visited Hagrid today. So one fact that we might
know about the world. And then we take another fact. Harry visited Hagrid or
Dumbledore today, but not both. So it tells us something
about the world. That Harry either visited
Hagrid but not Dumbledore, or Harry visited
Dumbledore but not Hagrid. And now we have a third piece
of information about the world that Harry visited Dumbledore today. So we now have three
pieces of information now. Three facts inside of a knowledge base,
so to speak-- information that we know. And now we, as humans, can
try and reason about this, and figure out based on
this information, what additional information
can we begin to conclude? And well, looking at
these last two statements, Harry either visited Hagrid
or Dumbledore but not both, and we know that Harry
visited Dumbledore today. Well, then it's pretty
reasonable that we could draw the conclusion that,
you know what, Harry must not have visited Hagrid today. Because based on a combination
of these two statements, we can draw this inference, so to speak. A conclusion that Harry
did not visit Hagrid today. But it turns out we can even do a little
bit better than that-- get some more information-- by taking a
look at this first statement and reasoning about that. This first statement says, if it didn't
rain, then Harry visited Hagrid today. So what does that mean? In all cases where it didn't rain,
then we know that Harry visited Hagrid. But if we also know now that
Harry did not visit Hagrid, then it tells us something
about our initial premise that we were thinking about. In particular, it tells
us that it did rain today. Because we can reason if it didn't rain,
that Harry would have visited Hagrid. But we know for a fact that
Harry did not visit Hagrid today. So it's this kind of reasoning,
the sort of logical reasoning where we use logic
based on the information that we know in order to take
information and reach conclusions. That is going to be the focus of what
we're going to be talking about today. How can we make our artificial
intelligence logical so that they can perform the same kinds of
deduction, the same kinds of reasoning that we've been doing so far. Of course, humans reason about logic
generally in terms of human language. That I just now was speaking
in English, talking in English, about these sentences and
trying to reason through how it is that they relate to one another. We're going to need to be a
little bit more formal when we turn our attention
to computers and being able to encode this notion of logic,
and truthhood and falsehood inside of a machine. So we're going to need to introduce a
few more terms and a few symbols that will help us reason
through this idea of logic inside of an artificial intelligence. And we'll begin with
the idea of a sentence. Now, a sentence in a natural
language like English is just something that I'm saying,
like what I'm saying right now. In the context of AI though, a sentence
is just an assertion about the world in what we're going to call a
knowledge representation language, some way of representing
knowledge inside of our computers. And the way that we're going
to spend most of today, reasoning about knowledge,
is through a type of logic known as propositional logic. There are a number of different types
of logic, some of which we'll touch on. But propositional logic is based
on a logic of propositions, or just statements about the world. And so we begin in propositional
logic with the notion of propositional symbols. We will have certain symbols
that are oftentimes just letters, something like P or Q or R,
where each of those symbols is going to represent some fact
or sentence about the world. So P, for example, might represent
the fact that it is raining. And so P is going to be a symbol
that represents that idea. And Q, for example, might represent
Harry visited Hagrid today. Each of these propositional
symbols represents some sentence or some fact about the world. But in addition to just having
individual facts about the world, we want some way to connect these
propositional symbols together in order to reason more complexly about
other facts that might exist inside of the world in which we're reasoning. So in order to do that, we'll need to
introduce some additional symbols that are known as logical connectives. Now, there are a number of
these logical connectives, but five of the most important, and
the ones we're going to focus on today, are these five up here, each
represented by a logical symbol. Not is represented by this symbol here. And is represented as sort
of an upside-down V. Or is represented by a V shape. Implication-- and we'll talk about
what that means in just a moment-- is represented by an arrow. And biconditional-- again, we'll talk
about what that means in a moment-- is represented by these double arrows. But these five logical
connectives are the main ones we're going to be focusing on
in terms of thinking about how it is that a computer
can reason about facts and draw conclusions based
on the facts that it knows. But in order to get there
we need to take a look at each of these logical connectives
and build up an understanding for what it is that they actually mean. So let's go ahead and
begin with the not symbol. So this not symbol here. And what we're going to show for
each of these logical connectives is what we're going
to call a truth table. A table that demonstrates
what this word not means when we attach it to a
propositional symbol or any sentence inside of our logical language. And so the truth table for
not is shown right here. If P-- some propositional symbol
or some other sentence, even-- is false, then not P is true. And if P is true, then not P is false. So you can imagine
placing this not symbol in front of some sentence
of propositional logic. Just says the opposite of that. So if, for example, P
represented it is raining, then not P would represent the
idea that it is not raining. And as you might expect, if P is false,
meaning if the sentence it is raining is false, well, then the
sentence not P must be true. The sentence that it is not
raining is, therefore, true. So not, you can imagine, just takes
whatever is in P and it inverts it. It turns false into true,
and true into false. Much analogously to what
the English word not means. Just taking whatever comes after it
and inverting it to mean the opposite. Next up, and also very
English-like, is this idea of and, represented by this upside-down
V shape, or this point shape. And as opposed to just taking a
single argument the way not does-- we have P and we have not P-- and is going to combine
two different sentences and propositional logic together. So I might have one sentence
P and another sentence Q, and I want to combine them
together to say P and Q. And the general logic for
what P and Q means is it means that both of
its operands are true. P is true, and also, Q is true. And so here's what that
truth table looks like. This time we have two
variables, P and Q. And when we have two variables, each
of which can be in two possible states, true or false, that
leads to two squared, or four, possible combinations
of truth and falsehood. So we have P is false and Q is false. We have P is false and Q is true. P is true and Q is false. And then P and Q both are true. And those are the only
four possibilities for what P and Q could mean. And in each of those situations,
this third column here, P and Q, is telling us a little bit about what it
actually means for P and Q to be true. And we see that the only case where
P and Q is true is in this fourth row here, where P happens to be
true, Q also happens to be true. And in all other situations, P and
Q is going to evaluate to false. So this, again, is much in line with
what our intuition of and might mean. If I say P and Q, I probably mean
that I expect both P and Q to be true. Next up, also potentially
consistent with what we mean, is this word or, represented
by this V shape, sort of an upside-down and symbol. And or, as the name might suggest,
is true if either of its arguments are true. As long as P is true or Q is true,
then P or Q is going to be true. Which means the only time that P or
Q is false is if both of its operands are false. If P is false and Q is false,
then P or Q is going to be false. But in all other cases, if at
least one of the operands is true-- maybe they're both true-- in which case P or Q is
going to evaluate to true. Now, this is mostly consistent with the
way that most people might use the word or, in the sense of speaking
the word or in normal English. Though there is sometimes
when we might say or where we mean P or Q, but not both. Or we mean sort of it can
only be one or the other. It's important to note that this symbol
here, this or, means P or Q or both. That those are totally OK. As long as either or both of
them are true, then the or is going to evaluate to be true as well. It's only in the case
where all of the operands are false that P or Q ultimately
evaluates to false as well. In logic there's another symbol
known as the exclusive or, which encodes this idea of exclusivity of,
like, one or the other, but not both. But we're not going to be
focusing on that today. Whenever we talk about or, we're
always talking about either or both, in this case, as represented
by this truth table here. So that now is not, and an, and or. And next step is what we might
call implication, as denoted by this arrow symbol. So we have P and Q.
And this sentence here will generally read as
P implies Q. And what P implies Q means is that if P
is true, then Q is also true. So I might say something like, if it
is raining, then I will be indoors. Meaning it is raining
implies I will be indoors is the logical sentence
that I'm saying there. And the truth table for this can
sometimes be a little bit tricky. So obviously, if P is true and Q is
true, then P implies Q, that's true. That definitely makes sense. And it should also stand to reason
that when P is true and Q is false, then P implies Q is false. Because if I said to you, if it is
raining, then I will be indoors, and it is raining, but I'm
not indoors, well, then it would seem to be that my
original statement was not true. P implies Q means that if P is
true, then Q also needs to be true. And if it's not, well, then
the statement is false. Also worth noting, though, is
what happens when P is false. When P is false, the implication
makes no claim at all. If I say something like, if it is
raining, then I will be indoors, and it turns out it's not
raining, then in that case, I am not making any
statement as to whether or not I will be indoors or not. P implies Q just means that
if P is true, Q must be true. But if P is not true, then
we make no claim about whether or not Q is true at all. So in either case, if P is
false, it doesn't matter what Q is, whether it's false or true. We're not making any
claim about Q whatsoever. We can still evaluate
the implication to true. The only way that the
implication is ever false is if our premise, P is true, but
the conclusion that we're drawing, Q, happens to be false. So in that case, we would say P
does not imply Q in that case. Finally, the last connective that
we'll discuss is this biconditional. You can think of a biconditional as a
condition that goes in both directions. So originally, when I said
something like, if it is raining, then I will be indoors. I didn't say what would
happen if it wasn't raining. Maybe I'll be indoors,
maybe I'll be outdoors. This biconditional you can
read as an if and only if. So I can say, I will be indoors
if and only if it is raining. Meaning if it is raining,
then I will be indoors. And if I am indoors, it's reasonable
to conclude that it is also raining. So this biconditional is only
true when P and Q are the same. So if P is true and Q is true, then
this biconditional is also true-- P implies Q. But also
the reverse is true. Q also implies P. So if P and
Q both happen to be false, we would still say it's true. But in any of these other two
situations, this P if and only if Q is going to ultimately
evaluate to false. So a lot of trues and
falses going on there, but these five basic
logical connectives are going to form the core of the
language of propositional logic, the language that we're going to
use in order to describe ideas, and the language that we're going to
use in order to reason about those ideas in order to draw conclusions. So let's now take a look at
some of the additional terms that we'll need to know
about in order to go about trying to form this
language of propositional logic, and writing AI that's actually able
to understand this sort of logic. The next thing we're going
to need is the notion of what is actually
true about the world. We have a whole bunch of
propositional symbols-- P and Q and R and maybe others. But we need some way of knowing, like,
what actually is true in the world. Is P true or false, is Q true
or false, so on and so forth. And to do that, we'll introduce
the notion of a model. A model just assigns a truth value where
a truth value is either true or false to every propositional symbol. In other words, it's creating what
we might call a possible world. So let me give an example. If, for example, I have
two propositional symbols, P is it is raining, and
Q is it is a Tuesday, a model just takes each
of these two symbols and assigns a truth value to
them, either true or false. So here's a sample model. In this model, in other
words, in this possible world, it is possible that P is true,
meaning it is raining, and Q is false, meaning it is not a Tuesday. But there are other possible
worlds or other models as well. There is some model where both
of these variables are true. Some model where both of
these variables are false. In fact, if there are N variables that
are propositional symbols like this, that are either true or false,
then the number of possible models is two to the N, because each
of these possible models-- possible variables
within my model could be set to either true or false, if I
don't know any information about it. So now that I have the symbols-- the symbols and the
connectives that I'm going to need in order to construct
these parts of knowledge, we need some way to
represent that knowledge. And to do so, we're going
to allow our AI access to what we'll call a knowledge base. And a knowledge base is
really just a set of sentences that our AI knows to be true. Some set of sentences
in propositional logic that are things that our
AI knows about the world. And so we might tell our AI
some information, information about a situation that
it finds itself in, or situation about a problem that
it happens to be trying to solve. And we would give that
information to the AI, that the AI would store
inside of its knowledge base. And what happens next
is the AI would like to use that information
in the knowledge base to be able to draw conclusions
about the rest of the world. And what do those conclusions look like? Well, to understand
those conclusions, we'll need to introduce one more
idea, one more symbol, and that is the notion of entailment. So this sentence here, with this double
turnstile and these Greek letters-- this is the Greek letter alpha
and the Greek letter beta-- and we read this as alpha entails beta. And alpha and beta here are just
sentences in propositional logic. And what this means is that alpha
entails beta means that in every model, in other words, in every possible
world in which sentence a is true-- or sentence alpha is true, then
sentence beta is also true. So if something entails something
else, if alpha entails beta, it means that if I
know alpha to be true, then beta must, therefore, also be true. So if my alpha is something like, I
know that it is a Tuesday in January, then a reasonable beta
might be something like, I know that it is January. Because in all worlds, where
it is a Tuesday in January, I know for sure that it must
be January, just by definition. This first statement, or
sentence about the world, entails the second statement. And we can reasonably use deduction,
based on that first sentence, to figure out that the second
sentence is, in fact, true as well. And ultimately, it's
this idea of entailment that we're going to try and
encode into our computer. We want our AI agent to
be able to figure out what the possible entailments are. We want our AI to be able to take
these three sentences, sentences like, if it didn't rain, Harry visited Hagrid. That Harry visited Hagrid
or Dumbledore but not both. And that Harry visited Dumbledore. And just using that
information, we'd like our AI to be able to infer, or figure out,
that using these three sentences inside of a knowledge base, we
can draw some conclusions. In particular, we can
draw the conclusions here that one, Harry did
not visit Hagrid today. And we can draw the entailment two,
that it did, in fact, rain today. And this process is known as inference. And that's what we're
going to be focusing on today, this process of deriving
new sentences from old ones. That I give you these three sentences,
you put them in the knowledge base in, say, the AI, and the AI is able to
use some sort of inference algorithm to figure out that these two
sentences must also be true. And that is how we define inference. So let's take a look
at an inference example to see how we might actually go about
inferring things in a human sense, before we take a more algorithmic
approach to see how we could encode this idea of inference in AI. And we'll see there are a number of
ways that we can actually achieve this. So again, we'll deal with a
couple of propositional symbols. We'll deal with P, Q and
R. P is it is a Tuesday. Q is it is raining. And R is Harry will go for a run. Three propositional symbols that
we are just defining to mean this. We're not saying anything yet about
whether they're true or false. We're just defining what they are. Now we'll give ourselves, or an
AI, access to a knowledge base, abbreviated to KB, to knowledge
that we know about the world. We know this statement. All right, so let's try to parse it. The parentheses here are
just used for precedent, so we can see what associates with what. But you would read this
as P and not Q implies R. All right, so what does that mean? Let's put it piece by piece. P is it is a Tuesday. Q is it is raining. So not Q is it is not raining. And implies R is Harry
will go for a run. So the way to read this entire sentence
in human natural language at least, is if it is a Tuesday and it is not
raining, then Harry will go for a run. So if it is a Tuesday and it is not
raining, then Harry will go for a run. And that is now inside
of our knowledge base. And let's now imagine that our
knowledge base has two other pieces of information as well. It has information that P is
true, that it is a Tuesday. And we also have the information
not Q, that it is not raining. That this sentence Q, it is
raining, happens to be false. And those are the three
sentences that we have access to. P and not Q implies R, P and not Q. Using that information, we should
be able to draw some inferences. P and not Q is only true if
both P and not Q are true. Well, all right, we know that P is true. And we know that not Q is true. So we know that this
whole expression is true. And the definition of implication is if
this whole thing on the left is true, then this thing on the
right must also be true. So if we know that P and not Q is
true, then R must be true as well. So the inference we should be
able to draw from all of this is that R is true, and we know
that Harry will go for a run, by taking this knowledge inside of our
knowledge base and being able to reason based on that idea. And so this ultimately, is the
beginning of what we might consider to be some sort of inference algorithm. Some process that we can use to
try and figure out whether or not we can draw some conclusion. And ultimately, what these inference
algorithms are going to answer is the central question
about entailment. Given some query about
the world, something we're wondering about the world,
and we'll call that query alpha, the question we want to ask,
using these inference algorithms, is does it KB, our knowledge
base, entail alpha? In other words, using
only the information we know inside of our knowledge base,
the knowledge that we have access to, can we conclude that this
sentence alpha is true? And that's ultimately
what we would like to do. So how can we do that? How can we go about writing an algorithm
that can look at this knowledge base and figure out whether or not
this query alpha is actually true? Well, it turns out there are a couple
of different algorithms for doing so. And one of the simplest, perhaps,
is known as model checking. Now remember, that a model
is just some assignment of all of the propositional symbols
inside of our language to a truth value, true or false. And you can think of a
model as a possible world. That there are many possible
worlds where different things might be true or false. And we can enumerate all of them. And the model checking
algorithm does exactly that. So what does our model
tracking algorithm do? Well, if we wanted to
determine if our knowledge base entails some query
alpha, then we are going to enumerate all possible models. In other words, consider all
possible values of true and false for our variables. All possible states in
which our world can be in. And if in every model where
our knowledge base is true, alpha is also true, then we know that
the knowledge base entails alpha. So let's take a closer
look at that sentence and try and figure out
what it actually means. If we know that in every model, in
other words, in every possible world, no matter what assignment of true
and false to variables you give, if we know that whenever
our knowledge is true-- what we know to be true is true-- that this query alpha is also true. Well, then it stands to reason that
as long as our knowledge base is true, then alpha must also be true. And so this is going to form the
foundation of our model checking algorithm. We're going to enumerate
all of the possible worlds, and ask ourselves, whenever the
knowledge base is true, is alpha true? And if that's the case, then
we know alpha to be true. And otherwise, there is no entailment. Our knowledge base
does not entail alpha. All right, so this is
a little bit abstract. But let's take a look at an example to
try and put real propositional symbols to this idea. So again, we'll work
with the same example. P is it is a Tuesday. Q is it is raining. R is Harry will go for a run. Our knowledge base contains
these pieces of information. P and not Q implies R. We
also know P, it is a Tuesday. And not Q, it is not raining. And our query, our alpha in this
case, the thing we want to ask is R. We want to know, is it guaranteed? Is it entailed that
Harry will go for a run. So the first step is to enumerate
all of the possible models. We have three propositional
symbols here, P, Q, and R, which means we have 2 to the
third power, or 8 possible models. All false. False, false, true. False, true, false. False, true, true. Et cetera. Eight possible ways you could assign
true and false to all of these models. And we might ask in each one of
them, is the knowledge base true? Here are the set of things that we know. In which of these worlds could this
knowledge base possibly apply to? In which world is this
knowledge base true? Well, in the knowledge
base, for example, we know P. Like, we know it is a Tuesday. Which means we know that these four--
first four rows-- where P is false, none of those are going to
be true, are going to work for this particular knowledge base. Our knowledge base is
not true in those worlds. Likewise, we also know not Q.
We know that it is not raining. So any of these models where Q is
true, like these two and these two here, those aren't going to work either
because we know that Q is not true. And finally, we also
know that P and not Q implies R. Which means
that when P is true-- where P is true here-- and Q is false-- Q is false in these two-- then R must be true. And if ever P is true, Q is
false, but R is also false, well, that doesn't satisfy
this implication here. That implication does not hold
true under those situations. So we could say that
for our knowledge base, we can conclude under which of these
possible worlds is our knowledge base true, and under which of the possible
worlds is our knowledge base false. And it turns out there is
only one possible world where our knowledge base is actually true. In some cases, there might
be multiple possible worlds where the knowledge base is true. But in this case it just so
happens that there's only one. One possible world where
we can definitively say something about our knowledge base. And in this case, we
would look at the query. The query of R. Is R true? R is true. And so as a result, we
can draw that conclusion. And so this is this
idea of model checking. Enumerate all the possible models,
and look in those possible models to see whether or not if
our knowledge base is true, is the query in question true as well. So let's now take a look
at how we might actually go about writing this in a
programming language like Python. Take a look at some
actual code that would encode this notion of
propositional symbols, and logic, and these connectives, like
and, and or, and not an implication, and so forth, and see what that
code might actually look like. So I've written in
advance a logic library. It's more detailed than we need
to worry about entirely today. But the important thing
is that we have one class for every type of logical symbol,
or connective, that we might have. So we just have one class
for logical symbols, for example, where every symbol
is going to represent and store some name for that particular symbol. And we also have a class for
not, that takes an operand. So we might say not one symbol
to say something is not true, or some other sentence is not true. We have one for and, one
for or, so on and so forth. And I'll just demonstrate
how this works. And you can take a look at
the actual logic.py later on. But go ahead and call
this file, Harry.py. We're going to store information
about this world of Harry Potter, for example. So I'll go ahead and import
from my logic module. I'll import everything. And in this library, in order to create
a symbol, you use capital S symbol. And I'll create a symbol for rain
to mean it is raining, for example. And I'll create a symbol
for Hagrid to mean Harry visited Hagrid is what
the symbol is going to mean. So this symbol means it is raining. This symbol means Harry visited Hagrid. And I'll add another
symbol called Dumbledore for Harry visited Dumbledore. Now I'd like to save these symbols
so that I can use them later, as I do some logical analysis. So I'll go ahead and save each
one of them inside of a variable. So, like, rain, Hagrid,
and Dumbledore, that you could call the variables anything. And now that I have
these logical symbols, I can use logical connectives
to combine them together. So for example, if I have a
sentence like, and rain and Hagrid, for example-- which is not necessarily true,
but just for demonstration-- I can now try and print
out sentence.formula, which is a function I wrote that takes
a sentence and propositional logic and just prints it out so
that we, the programmers, can now see this in order to get an
understanding for how it actually works. So if I run Python
Harry.py, what we'll see is this sentence and propositional
logic, rain and Hagrid. This is the logical
representation of what we have here in our
Python program of saying and, whose arguments
are rain and Hagrid. So we're saying rain and
Hagrid by encoding that idea. And this is quite common in
Python object-oriented programming where you have a number
of different classes, and you pass arguments into
them in order to create a new and object, for example, in
order to represent this idea. But now what I'd like to do is
somehow encode the knowledge that I have about the
world, in order to solve that problem from the
beginning of class, where we talked about trying to
figure out who Harry visited, and trying to figure out if it's
raining or if it's not raining. And so what knowledge do I have? I'll go ahead and create a
new variable called knowledge. And what do I know? Well, I know the very
first sentence that we talked about was the idea that
if it is not raining, then Harry will visit Hagrid. All right. How do I encode the idea
that it is not raining? Well, I can use not and
then the rain symbol. So here's me saying
that it is not raining. And now the implication is that if it is
not raining, then Harry visited Hagrid. So I'll wrap this
inside of an implication to say, if it is not raining, this
first argument to the implication, well, then Harry visited Hagrid. So I'm saying implication, the
premise is that it's not raining. And if it is not raining,
then Harry visited Hagrid. And I can print out knowledge.formula
to see the logical formula equivalent of that same idea. So I run Python of Harry.py,
and this is the logical formula that we see as a result, which
is a text-based version of what we were looking at before. That if it is not raining, then that
implies that Harry visited Hagrid. But there was additional information
that we had access to as well. In this case, we had access
to the fact that Harry visited either Hagrid or Dumbledore. So how do I encode that? Well, this means that
in my knowledge, I've really got multiple pieces
of knowledge going on. I know one thing, and another
thing, and another thing. So I'll go ahead and wrap all of
my knowledge inside of an and. And I'll move things on a new
line just for good measure. But I know multiple things. So I'm saying knowledge is an and
of multiple different sentences. I know multiple different
sentences to be true. One such sentence that I know
to be true is this implication that if it is not raining,
then Harry visited Hagrid. Another such sentence that I know
to be true is or Hagrid Dumbledore. In other words, so Hagrid
or Dumbledore is true, because I know that Harry
visited Hagrid or Dumbledore. But I know more than that, actually. That initial sentence from
before said that Harry visited Hagrid or Dumbledore, but not both. So now I want a sentence that'll
encode the idea that Harry didn't visit both Hagrid and Dumbledore. Well, the notion of Harry
visiting Hagrid and Dumbledore would be represented like this. And of Hagrid and Dumbledore. And if that is not true, I want
to say not that, then I'll just wrap this whole thing inside of a not. So now these three lines, line 8
says that if it is not raining, then Harry visited Hagrid. Line 9 says Harry visited
Hagrid or Dumbledore. And line 10 says Harry didn't
visit both Hagrid and Dumbledore. That it is not true that both the
Hagrid symbol and the Dumbledore symbol are true. Only one of them can be true. And finally, the last piece
of information that I knew was the fact that Harry
visited Dumbledore. So these now are the pieces
of knowledge that I know. One sentence, and another
sentence, and another, and another. And I can print out what I know, just
to see it a little bit more visually. And here now is a logical
representation of the information that my computer is now
internally representing using these various
different Python objects. And again, take a look at logic.py if
you want to take a look at how exactly it's implementing this. But no need to worry too much
about all of the details there. We're here saying that if it is not
raining, then Harry visited Hagrid. We're saying that Hagrid
or Dumbledore is true. And we're saying it is not the case
that Hagrid and Dumbledore is true. That they're not both true. And we also know that
Dumbledore is true. So this long, logical sentence
represents our knowledge base. It is the thing that we know. And now what we like to do is we like
to use model checking to ask a query. To ask a question like,
based on this information, do I know whether or not it's raining? And we, as humans, we're able
to logic our way through it and figure out that, all right, based
on these sentences we can conclude this and that to figure out that
yes, it must have been raining. But now we'd like for the
computer to do that as well. So let's take a look at the
model checking algorithm that is going to follow that same
pattern that we drew out in pseudocode a moment ago. So I've defined a
function here in logic.py that you can take a look
at called model check. Model check takes two arguments,
the knowledge that I already know and the query. And the idea is, in order
to do model checking, I need to enumerate all
of the possible models. And for each of the possible
models, I need to ask myself, is the knowledge base true,
and is the query true? So the first thing I
need to do is somehow enumerate all of the possible models. Meaning for all possible
symbols that exist, I need to assign true and
false to each one of them and see whether or not it's still true. And so here is the way
we're going to do that. We're going to start-- so I've defined another
helper function internally that we'll get to in just a moment. But this function starts by
getting all of the symbols, in both the knowledge and
the query, by figuring out what symbols am I dealing with? In this case, the
symbols I'm dealing with are rain, and Hagrid, and Dumbledore. But there might be other symbols,
depending on the problem. And we'll take a look soon at
some examples of situations where ultimately, we're going to
need some additional symbols in order to represent the problem. And then we're going to run
this check all function, which is a helper function that's basically
going to recursively call itself, checking every possible configuration
of propositional symbols. So we start out by looking at this
check all function, and what do we do? So if not symbols means if we
finish assigning all of the symbols. We've assigned every symbol of value. So far we haven't done that, but
if we ever do, then we check. In this model, is the knowledge true? That's what this line is saying. If we evaluate the knowledge,
propositional logic formula, using the models assignment of
truth values, is the knowledge true? If the knowledge is true, then we should
return true, only if the query is true. Because if the knowledge is true, we
want the query to be true as well, in order for there to be entailment. Otherwise, we don't know
that there-- otherwise, there won't be an entailment. If there's ever a situation where
what we know in our knowledge is true, but the query, the thing we're
asking, happens to be false. So this line here is
checking that same idea. That in all worlds where the knowledge
is true, the query must also be true. Otherwise, we can just return true,
because if the knowledge isn't true, then we don't care. This is equivalent to
when we were enumerating this table from a moment ago. In all situations where the
knowledge base wasn't true-- all of these seven rows here-- we didn't care whether or not
our query was true or not. We only care to check
whether the query is true when the knowledge
base is actually true, which was just this green
highlighted row right there. So that logic is encoded
using that statement there. And otherwise, if we haven't
assigned symbols yet, which we haven't seen anything
yet, then the first thing we do is pop one of the symbols. I make a copy of the symbols first,
just to save an existing copy. But I pop one symbol off
of the remaining symbols, so that I just pick
one symbol at random. And I create one copy of the
model where that symbol is true, and I create a second copy of the
model where that symbol is false. So I now have two copies of the model. One where the symbol is true, and
one where the symbol is false. And I need to make sure
that this entailment holds in both of those models. So I recursively check all on the
model where the statement is true, and check all on the model
where the statement is false. So again, you can take
a look at that function to try to get a sense for how
exactly this logic is working. But in effect, what it's doing
is recursively calling this check all function again and again and again. And on every level of the
recursion we're saying, let's pick a new symbol that
we haven't yet assigned. Assign it to true. And assign it to false. And then check to make sure that
the entailment holds in both cases. Because ultimately, I need to
check every possible world. I need to take every
combination of symbols and try every combination
of true and false in order to figure out whether the
entailment relation actually holds. So that function we've written for you. But in order to use that function
inside of Harry.py, what I'll write is something like this. I would like to model check,
based on the knowledge, and then I provide, as a second
argument, what the query is. What the thing I want to ask is. And what I want to ask in
this case is, is it raining? So model check, again,
takes two arguments. The first argument is the
information that I know. This knowledge. Which in this case, is this information
that was given to me at the beginning. And the second argument, rain, is
encoding the idea of the query. What am I asking? I would like to ask,
based on this knowledge, do I know for sure that it is raining? And I can try and print
out the result of that. And when I run this program,
I see that the answer is true. That based on this information, I can
conclusively say that it is raining. Because using this model
checking algorithm, we were able to check that in every
world where this knowledge is true, it is raining. In other words, there's no world
where this knowledge is true and it is not raining. So you can conclude that
it is, in fact, raining. And this sort of logic
can be applied to a number of different types of problems. That if confronted with a problem
where some sort of logical deduction can be used in order
to try to solve it, you might try thinking about
what propositional symbols you might need in order to
represent that information. And what statements
and propositional logic you might use in order to encode
that information which you know. And this process of
trying to take a problem and figure out what propositional
symbols to use in order to encode that idea, or how
to represent it logically is known as knowledge engineering. That software engineers and AI
engineers will take a problem and try and figure out how to
distill it down into knowledge that is representable by a computer. And if we can take any general
purpose problem, some problem that we find in the human world,
and turn it into a problem that computer's know how
to solve, as by using any number of different
variables, well, then, we can take a computer that
is able to do something like model checking or some
other inference algorithm, and actually figure out
how to solve that problem. So now we'll take a look at two or
three examples of knowledge engineering and practice. Of taking some problem and figuring
out how we can apply logical symbols and use logical formulas to
be able to encode that idea. And we'll start with a very popular
board game in the US and the UK, known as Clue. Now, in the game of Clue, there's
a number of different factors that are going on. But the basic premise of the game
if you've never played it before, is that there are a number
of different people-- for now we'll just use three,
Colonel Mustard, Professor Plum, and Miss Scarlet. There are a number of different
rooms, like a ballroom, a kitchen, and a library. And there are a number of different
weapons-- a knife, a revolver, and a wrench. And three of these-- one person,
one room, and one weapon-- is the solution to the mystery-- the murderer and what room they were
in, and what weapon they happen to use. And what happens at the beginning of
the game is that all these cards are randomly shuffled together, and
three of them-- one person, one room, and one weapon-- are placed into a sealed
envelope that we don't know. And we would like to figure out, using
some sort of logical process, what's inside the envelope. Which person, which
room, and which weapon. And we do so by looking at some,
but not all, of these cards here. By looking at these cards to try and
figure out what might be going on. And so this is a very
popular game, but let's now try and formalize it and see
if we could train a computer to be able to play this game by
reasoning through it logically. So in order to do this
we'll begin by thinking about what propositional symbols
we're ultimately going to need. Remember again, that
propositional symbols are just some symbol, some variable, that can
be either true or false in the world. And so in this case, the
propositional symbols are really just going to correspond
to each of the possible things that could be inside the envelope. Mustard is a propositional
symbol, that in this case will just be true, if Colonel
Mustard is inside the envelope, if he is the murderer. And false otherwise. And likewise, for Plum for Professor
Plum, and Scarlet for Miss Scarlet, and likewise for each of the
rooms, and for each of the weapons. We have one propositional
symbol for each of these ideas. Then using those
propositional symbols we can begin to create logical
sentences, create knowledge that we know about the world. So for example, we know that
someone is the murderer. That one of the three people
is, in fact, the murderer. And how would we encode that? Well, we don't know for
sure who the murderer is, but we know it is one person, or the
second person, or the third person. So I could say something like
this: Mustard, or Plum, or Scarlet. And this piece of knowledge encodes
that one of these three people is the murderer. We don't know which, but one of
these three things must be true. What other information do we know? Well, we know that, for
example, one of the rooms must have been the room in the envelope. That the crime was committed either
in the ballroom, or the kitchen, or the library. Again, right now we don't
know which, but this is knowledge we know at the outset--
knowledge that one of these three must be inside the envelope. And likewise we can say the
same thing about the weapon. That it was either the knife,
or the revolver, or the wrench. That one of those weapons must
have been the weapon of choice, and therefore, the
weapon in the envelope. And then as the game
progresses, the game play works by people get
various different cards. And using those cards, you
can deduce information. That if someone gives
you a card, for example, I have the Professor
Plum card in my hand, then I know the Professor Plum
card can't be inside the envelope. I know that Professor
Plum is not the criminal. So I know a piece of information,
like not Plum, for example. I know that Professor
Plum has to be false. This propositional symbol is not true. And sometimes I might not know for
sure that a particular card is not in the middle. But sometimes, someone
will make a guess, and I'll know that one of three
possibilities is not true. Like someone will guess Colonel Mustard
in the library with the revolver, or something to that effect. And in that case, a card might
be revealed that I don't see. But if it is a card, and it is either
Colonel Mustard, or the revolver, or the library, then I know
that at least one of them can't be in the middle. So I know something like, it is either
not Mustard, or it is not the library, or it is not the revolver. Now maybe multiple of these are not
true, but I know that at least one of Mustard, library and revolver
must, in fact, be false. And so this now is a propositional logic
representation of this game of Clue. A way of encoding the knowledge
that we know inside this game using propositional logic that a
computer algorithm, something like model checking that
we saw a moment ago, can actually look at and understand. So let's now take a look at some code
to see how this algorithm might actually work in practice. All right. So I'm now going to open
up a file called clue.py, which I've started already. And what we'll see here is I've
defined a couple of things. I've defined some symbols initially. Notice I have a symbol for Colonel
Mustard, a symbol for Professor Plum, a symbol for Miss Scarlet,
all of which I put inside of this list of characters. I have a symbol for
ballroom, and kitchen, and library inside of a list of rooms. And then I have symbols for
knife, and revolver, and wrench. These are my weapons. And so all of these characters
and rooms and weapons altogether, those are my symbols. And now I also have this
check knowledge function. And what the check knowledge function
does is it takes my knowledge, and it's going to try and draw
conclusions about what I know. So for example, we'll loop over all of
the possible symbols and we'll check. Do I know that that symbol is true? And a symbol is going to be something
like Professor Plum, or the knife, or the library. And if I know that it
is true, in other words, I know that it must be
the card in the envelope, then I'm going to print
out, using a function called C print, which prints things in color. I'm going to print out the word yes,
and I'm going to print that in green, just to make it very clear to us. And if we're not sure
that the symbol is true, maybe I can check to see if I'm
sure that the symbol is not true. Like, if I know for sure that it
is not Professor Plum, for example. And I do that by running
model check again. This time checking if my
knowledge is not the symbol. If I know for sure that
the symbol is not true. And if I don't know for sure
that the symbol is not true, because I say elif not model
check, meaning I'm not sure that the symbol is
false, well, then I'll go ahead and print out
maybe next to the symbol. Because maybe the symbol is true. Maybe it's not. I don't actually know. So what knowledge do I actually have? Well, let's try and
represent my knowledge now. So my knowledge is, I know a couple
of things so I'll put them in an and. And I know that one of the three
people must be the criminal. So I know or Mustard, Plum, Scarlet. This is my way of encoding that it is
either Colonel Mustard, or Professor Plum, or Miss Scarlet. I know that it must have
happened in one of the rooms. So I know or ballroom,
kitchen, library, for example. And I know that one of the weapons
must have been used as well. So I know or knife, revolver, wrench. So that might be my initial knowledge. That I know that it must
have been one of the people. I know it must have been
in one of the rooms. And I know that it must have
been one of the weapons. And I can see what that knowledge
looks like as a formula, by printing out knowledge.formula. So I'll run Python clue.py. And here now is the information
that I know in logical format. I know that it is Colonel Mustard,
or Professor Plum, or Miss Scarlet. And I know that it is the ballroom,
the kitchen, or the library. And I know that it is the knife,
the revolver, or the wrench. But I don't know much more than that. I can't really draw
any firm conclusions. And in fact, we can see
that if I try and do-- let me go ahead and run my knowledge
check function on my knowledge. Now let's check is
this function that I-- or check knowledge rather. Is this function that I just wrote
that looks over all of the symbols and tries to see what conclusions I can
actually draw about any of the symbols. So I'll go ahead and run clue.py
and see what it is that I know. And it seems that I don't
really know anything for sure. I have all three people are maybes. All three of the rooms are maybes. All three of the weapons are maybes. I don't really know anything
for certain just yet. But now let me try and add
some additional information and see if additional
information, additional knowledge, can help us to logically reason
our way through this process. And we are just going to
provide the information. Our AI is going to take care of
doing the inference and figuring out what conclusions it's able to draw. So I start with some cards, and
those cards tell me something. So if I have the Colonel
Mustard card, for example, I know that the Mustard
symbol must be false. In other words, Mustard is
not the one in the envelope. It's not the criminal. So I can say, knowledge
supports something called-- every and in this library supports .add,
which is a way of adding knowledge, or adding an additional logical
sentence to an and clause. So I can say,
knowledge.add, not Mustard. I happen to know, because
I have the Mustard card, that Colonel Mustard is not the suspect. And maybe I have a couple
of other cards, too. Maybe I also have a
card for the kitchen, so I know it's not the kitchen. And maybe I have another card that
says that it is not the revolver. So I have three cards-- Colonel Mustard, the
kitchen, and the revolver. And I encode that into my AI this way,
by saying it's not Colonel Mustard, it's not the kitchen, and it's not the
revolver, and I know those to be true. So now when I rerun clue.py,
we'll see that I've been able to eliminate some possibilities. Before I wasn't sure if it was the
knife, or the revolver, or the wrench. Knife was maybe, revolver
was maybe, wrench was maybe. Now I'm down to just the
knife and the wrench. Between those two, I don't know which
one it is-- they're both maybes-- but I've been able to eliminate
the revolver, which is one that I know to be false because
I have the revolver card. And so additional information might be
acquired over the course of this game, and we would represent that
just by adding knowledge to our knowledge set, or knowledge
base that we've been building here. So if, for example, we
additionally got the information that someone made a guess. Someone guessed, like, Miss Scarlet
in the library with the wrench. And we know that that card was revealed,
which means that one of those three cards-- either Miss Scarlet,
or the library, or the wrench-- one of those at minimum, must
not be inside of the envelope. So I could add some knowledge. Say knowledge.add, and I'm going
to add an or clause, because I don't know for sure which one it's not. But I know one of them
is not in the envelope. So it's either not Scarlet
or it's not the library. And or supports multiple arguments. I can say it's also or not the wrench. So at least one of those-- Scarlet, library, and wrench-- at
least one of those needs to be false. I don't know which, though. Maybe it's multiple, maybe it's
just one, but at least one I know needs to hold. And so now if I rerun
clue.py, I don't actually have any additional
information just yet. Nothing I can say conclusively. I still know that maybe it's Professor
Plum, maybe it's Miss Scarlet. I haven't eliminated any options. But let's imagine that I
get some more information. That someone shows me the
Professor Plum card, for example. So I say, all right. Let's go back here. Knowledge.add, not Plum. So I have the Professor Plum card. I know that Professor
Plum is not in the middle. I rerun clue.py, and right now,
I'm able to draw some conclusions. Now I've been able to
eliminate Professor Plum. And the only person that could
remaining be is Miss Scarlet. So I know, yes, Miss Scarlet,
this variable must be true. And I've been able to infer that based
on the information I already had. Now, between the ballroom and the
library, and the knife and the wrench, for those two, I'm still not sure. So let's add one more
piece of information. Let's say that I know that
it's not the ballroom. Someone has shown me the ballroom
card, so I know it's not the ballroom. Which means at this point, I should be
able to conclude that it's the library. Let's see. I'll say knowledge.add,
not the ballroom. And we'll go ahead and run that. And it turns out that after
all of this, not only can I conclude that I know
that it's the library. But I also know that the
weapon was the knife. And that might have been an inference
that was a little bit trickier. Something I wouldn't have
realized immediately. But the AI, via this
model checking algorithm, is able to draw that conclusion. That we know for sure
that it must be Miss Scarlet in the library with the knife. And how do we know that? Well, we know it from
this or clause up here. That we know that it's either not
Scarlet, or it's not the library, or it's not the wrench. And given that we know
that it is Miss Scarlet, and we know that it is the
library, then the only remaining option for the weapon is that
it is not the wrench, which means that it must be the knife. So we, as humans, now can go
back and reason through that, even though it might not
have been immediately clear. And that's one of the advantages of
using an AI or some sort of algorithm in order to do this,
is that the computer can exhaust all of these
possibilities and try and figure out what the solution actually should be. And so for that reason,
it's often helpful to be able to represent knowledge in this way. Knowledge engineering,
some situation, where we can use a computer to be able
to represent knowledge and draw conclusions based on that knowledge. And anytime we can translate
something into propositional logic symbols like this, type
of approach can be useful. So you might be familiar
with logic puzzles, where you have to puzzle your way through
trying to figure something out. This is what a classic logic
puzzle might look like. Something like Gildaroy,
Minerva, Pomona, and Horace each belong to a different
one of the four houses-- Gryffindor, Hufflepuff,
Ravenclaw, and Slytherin. And then we have some
information, that Gildaroy belongs to Gryffindor or Ravenclaw. Pomona does not belong in Slytherin. And Minerva does belong to Gryffindor. We have a couple of
pieces of information. And using that
information, we need to be able to draw some conclusions
about which person should be assigned to which house. And again, we can use the exact same
idea to try and implement this notion. So we need some propositional symbols. And in this case, the
propositional symbols are going to get a little
more complex, although, we'll see ways to make this a
little bit cleaner later on. But we'll need 16 propositional symbols. One for each person and house. So we need to say-- remember,
every propositional symbol is either true or false. So Gildaroy Gryffindor
is either true or false. Either he's in Gryffindor or he is not. Likewise, Gildaroy Hufflepuff
also true or false. Either it is true or it's false. And that's true for every
combination of person and house that we could come up with. We have some sort of propositional
symbol for each one of those. Using this type of
knowledge, we can then begin to think about what
types of logical sentences we can say about the puzzle. That if we know-- before even think about the
information we were given, we can think about the
premise of the problem. That every person is assigned
to a different house. So what does that tell us? Well, it tells us sentences like this. It tells us, like, Pomona Slytherin
implies not Pomona Hufflepuff. Something like if
Pomona is in Slytherin, then we know that Pomona
is not in Hufflepuff. And we know this for all four people
and for all combinations of houses. That no matter what you person
you pick, if they're in one house, then they're not in some other house. So I'll probably have a whole
bunch of knowledge statements that are of this four. That if we know Pomona's
in Slytherin, then we know Pomona is not in Hufflepuff. We were also given the information that
each person is in a different house. So I also have pieces of knowledge
that look something like this. Minerva Ravenclaw implies
not Gildaroy Ravenclaw. If they're all in different houses,
then if Minerva is in Ravenclaw, then we know that Gildaroy
is not in Ravenclaw as well. And I have a whole bunch
of similar sentences like this that are expressing that
idea for other people and other houses as well. And so in addition to
sentences of these form, I also have the knowledge
that was given to me. Information like, Gildaroy was
in Gryffindor or in Ravenclaw that would be represented like this. Gildaroy Gryffindor
or Gildaroy Ravenclaw. And then using these
sorts of sentences, I can begin to draw some
conclusions about the world. So let's see an example of this. We'll go ahead and actually try
and implement this logic puzzle to see if we can figure
out what the answer is. I'll go ahead and open up
puzzle.py where I've already started to implement this sort of idea. I've defined a list of
people and a list of houses. And I've, so far, created one symbol
for every person and for every house. That's what this double for loop
is doing-- looping over all people, looping over all houses, creating
a new symbol for each of them. And then I've added some information. I know that every person
belongs to a house, so I've added the information
for every person-- that person Gryffindor,
or person Hufflepuff, or person Ravenclaw,
or person Slytherin. That one of those four
things must be true. Every person belongs to a house. What other information do I know? I also know that only
one house per person. So no person belongs to multiple houses. So how does this work? Well, this is going to
be true for all people. So I'll loop over every person. And then I need to loop over
all different pairs of houses. The idea is I want to encode the idea
that if Minerva is in Gryffindor, then Minerva can't be in Ravenclaw. So I'll loop over all houses, h1. And I'll loop over all houses again, h2. And as long as they're
different, h1 not equal to H2, then I'll add to my knowledge
base this piece of information. That implication, in other words,
an if/then, if the person is in h1, then I know that they
are not in house h2. So these lines here
are encoding the notion that for every person, if
they belong to house one, then they are not in house two. And the other piece of
logic we need to encode is the idea that every house
can only have one person. In other words, if Pomona is
Hufflepuff, then nobody else is allowed to be in Hufflepuff either. And that's the same logic
but sort of backwards. I loop over all of the houses, and loop
over all different pairs of people. So I loop over people once,
loop over people again. And only do this when the people
are different, p1 not equal to p2. And I add the knowledge that
if, as given by the implication, if person one belongs
to the house, then it is not the case that person
two belongs to the same house. So here I'm just encoding
the knowledge that represents the problems constraints. I know that everyone's
in a different house. I know that any person can
only belong to one house. And I can now take my knowledge and
try and print out the information that I happen to know. So I'll go ahead and print
out knowledge.formula, just to see this in action. And I'll go ahead and skip
this for now, but we'll come back to this in a second. Let's print out the knowledge that
I know by running Python puzzle.py. It's a lot of information,
a lot that I have to scroll through, because there's
16 different variables all going on. But the basic idea if we
scroll up to the very top is I see my initial information. Gildaroy is either in Gryffindor,
or Gildaroy is and Hufflepuff, or Gildaroy is and Ravenclaw,
or Gildaroy is in Slytherin. And then way more information as well. So this is quite messy. More than we really
want to be looking at. And soon, too, we'll see ways of
representing this a little bit more nicely using logic. But for now, we can just say these are
the variables that we're dealing with. And now we'd like to
add some information. So the information we're going to
add is Gildaroy is in Gryffindor or he is in Ravenclaw. So that knowledge was given to us. So I'll go ahead and
say knowledge.add, and I know that either or Gildaroy
Gryffindor or Gildaroy Ravenclaw. One of those two things must be true. I also know that Pomona
was not in Slytherin. So I can say knowledge.add,
not this symbol. Not the Pomona Slytherin symbol. And then I can add the knowledge that
Minerva is in Gryffindor by adding the symbol Minerva Gryffindor. So those are the pieces
of knowledge that I know. And this loop here at the bottom
just loops over all of my symbols, checks to see if the knowledge entails
that symbol by calling this model check function again. And if it does, if we know the symbol
is true, we print out the symbol. So now I can run Python
puzzle.py, and Python is going to solve this puzzle for me. We're able to conclude that
Gildaroy belongs to Ravenclaw, Pomona belongs to Hufflepuff,
Minerva to Gryffindor, and Horace to Slytherin just by encoding
this knowledge inside the computer-- although, it was quite
tedious to do in this case-- and as a result, we were able to get
the conclusion from that as well. And you can imagine this being applied
to many sorts of different deductive situations. So not only these situations
where we're trying to deal with Harry Potter
characters in this puzzle. But if you've ever played
games like Mastermind where you're trying to figure out
which order different colors go in and trying to make predictions about
it, I could tell you, for example. Let's play a simplified version
of Mastermind where there are four colors-- red, blue, green, and yellow-- and they're in some order, but
I'm not telling you what order. You just have to make a
guess, and I'll tell you of red, blue, green, and
yellow, how many of the four you got in the right position. So a simplified version of this game. You might make a guess, like
red, blue, green, yellow. And I would tell you something
like, two of those four are in the correct position,
but the other two are not. Then you could reasonably make a guess
and say, all right, let's try this. Blue, red, green, yellow. Try switching two of them around. And this time maybe I tell you,
you know what, none of those are in the correct position. And the question then
is, all right, what is the correct order of these four colors? And we, as humans, could
begin to reason this through. All right, well, if none of these were
correct, but two of these were correct, well, it must have been because
I switched the red and the blue. Which means red and blue
here must be correct. Which means green and yellow
are probably not correct. You can begin to do this
sort of deductive reasoning. We can also equivalently try and
take this and encode it inside of our computer as well. And it's going to be very
similar to the logic puzzle that we just did a moment ago. So I won't spend too much time on this
code because it is fairly similar. But again, we have a
whole bunch of colors, and four different positions
in which those colors can be. And then we have some
additional knowledge. And I encode all of that knowledge. And you can take a look at
this code on your own time. But I just want to demonstrate
that when we run this code, run Python mastermind.py
and run and see what we get, we ultimately are able to
compute red in the zero position, blue in the one position,
yellow in the two position, and green in the three position
as the ordering of those symbols. Now, ultimately, what
you might have noticed is this process was
taking quite a long time. And, in fact, model checking is not
a particularly efficient algorithm. What I need to do in
order to model check is take all of my possible
different variables and enumerate all of the
possibilities that they could be in. If I have n variables, I have
2 to the n possible worlds that I need to be looking through in
order to perform this model checking algorithm. And this is probably not
tractable, especially as we start to get to much
larger and larger sets of data where you have many, many more
variables that are at play. Right here we only have a relatively
small number of variables, so this sort of approach
can actually work. But as the number of
variables increases, model checking becomes less and
less good of a way of trying to solve these sorts of problems. So while it might have been OK
for something like Mastermind to conclude that this is,
indeed, the correct sequence, where all four are in the correct
position, what we'd like to do is come up with some better ways to
be able to make inferences, rather than just enumerate all
of the possibilities. And to do so, what we'll transition to
next is the idea of inference rules. Some sort of rules that we can apply
to take knowledge that already exists and translate it into
new forms of knowledge. And the general way we'll
structure inference rule is by having a horizontal line here. Anything above the line is going
to represent a premise, something that we know to be true. And then anything below the
line will be the conclusion that we can arrive at
after we apply the logic, or from the inference rule that
we're going to demonstrate. So we'll do some of
these inference rules by demonstrating them in English
first, but then translating them into the world of
propositional logic so you can see what those inference
rules actually look like. So for example, let's
imagine that I have access to two pieces of information. I know, for example,
that if it is raining, then Harry is inside, for example. And let's say I also know it is raining. Then most of us could reasonably
then look at this information and conclude that, all
right, Harry must be inside. This inference rule is
known as modus ponens, and it's phrased more
formally in logic as this. If we know that alpha implies
beta, in other words, if alpha, then beta, and we also
know that alpha is true, then we should be able to
conclude that beta is also true. We can apply this inference rule to
take these two pieces of information and generate this new
piece of information. Notice that this is a totally different
approach from the model checking approach, where the approach was
look at all of the possible worlds and see what's true in
each of these worlds. Here, we're not dealing
with any specific world. We're just dealing with
the knowledge that we know and what conclusions we can
arrive at based on that knowledge. That I know that A implies B, and
I know A, and the conclusion is B. And this should seem like
a relatively obvious rule. But of course, if alpha than
beta, and we know alpha, then we should be able to
conclude that beta is also true. And that's going to be true for many,
maybe even all of the inference rules that we'll take a look at. You should be able to look
at them and say, yeah, of course that's going to be true. But it's putting these
all together, figuring out the right combination
of inference rules that can be applied that ultimately
is going to allow us to generate interesting knowledge inside of our AI. So that's modus ponens, this
application of implication. That if we know alpha, and we
know that alpha implies beta, then we can conclude beta. Let's take a look at another example. Fairly straightforward. Something like Harry is
friends with Ron and Hermione. Based on that information,
we can reasonably conclude Harry is friends with Hermione. That must also be true. And this inference rule is
known as and elimination. And what and elimination says, is
that if we have a situation where alpha and beta are both true-- I have information alpha and beta-- well, then just alpha is true. Or likewise, just beta is true. That if I know that both parts
are true, then one of those parts must also be true. Again, something obvious from the
point of view of human intuition. But a computer needs to be
told this kind of information. To be able to apply
the inference rule, we need to tell the computer that
this is an inference rule that you can apply so the computer
has access to it, and is able to use it in
order to translate information from one form to another. In addition to that, let's
take a look at another example of an inference rule. Something like, it is not true
that Harry did not pass the test. Bit of a tricky sentence
to parse so read it again. It is not true, or it is false,
that Harry did not pass the test. Well, if it is false that
Harry did not pass the test, then the only reasonable conclusion
is that Harry did pass the test. And so this, instead of
being and elimination, is what we call double
negation elimination. That if we have two negatives
inside of our premise, then we can just remove them altogether. They cancel each other out. One turns true to false, and the
other one turns false back into true. Phrased a little bit
more formally, we say that if the premise is not not alpha,
then the conclusion we can draw is just alpha. We can say that alpha is true. We'll take a look at a
couple more of these. If I have it is raining, then Harry
is inside, how do I reframe this? Well, this one is a little bit trickier. But if I know if it is
raining, then Harry is inside, then I conclude one of
two things must be true. Either it is not raining,
or Harry is inside. Now, this one's trickier, so
let's think about it a little bit. This first premise here, if it
is raining, then Harry is inside, is saying that if I know that it is
raining, then Harry must be inside. So what is the other possible case? Well, if Harry is not inside, then
I know that it must not be raining. So one of those two
situations must be true. Either it's not raining, or it is
raining, in which case Harry is inside. So the conclusion I can draw is either
it is not raining, or it is raining, so therefore, Harry is inside. So this is a way to translate if/then
statements into or statements. And this is known as
implication elimination. And this is similar to what we
actually did in the beginning when we were first looking at those very
first sentences about Harry and Hagrid and Dumbledore. And phrased a little
bit more formally, this says that if I have
the implication alpha implies beta, that I can draw the
conclusion that either not alpha or beta. Because there are only
two possibilities. Either alpha is true
or alpha is not true. So one of those possibilities
is alpha is not true. But if alpha is true, well,
then we can draw the conclusion that beta must be true. So either alpha is not true, or alpha is
true, in which case beta is also true. So this is one way to turn an
implication into just a statement about or. In addition to eliminating
implications, we can also eliminate biconditionals as well. So let's take an English example. Something like, it is raining
if and only if Harry is inside. And this if and only if really
sounds like that biconditional, that double arrow sign that we saw in
propositional logic not too long ago. And what does this actually mean
if we were to translate this? Well, this means that if it is
raining, then Harry is inside. And if Harry is inside,
then it is raining. The implication goes both ways. And this is what we would call
biconditional elimination. That I can take a biconditional,
A if and only if B, and translate that into
something like this. A implies B, and B implies A.
So many of these inference rules are taking logic that
uses certain symbols and turning them into different
symbols, taking an implication and turning it into an or. Or taking a biconditional and
turning it into implication. And another example of it
would be something like this. It is not true that both
Harry and Ron passed the test. Well, all right, how
do we translate that? What does that mean? Well, if it is not true that
both of them passed the test, well, then the reasonable conclusion we
might draw is that at least one of them didn't pass the test. So the conclusion is either
Harry did not pass the test, or Ron did not pass the test, or both. This is not an exclusive or. But if it is true that it is not
true, that both Harry and Ron passed the test, well, then either
Harry didn't pass the test or Ron didn't pass the test. And this type of law is
one of De Morgan's laws. Quite famous in logic, where the idea
is that we can turn an and into an or. We can take this and, that both
Harry and Ron passed the test, and turn it into an or by
moving the nots around. So if it is not true that
Harry and Ron passed the test, well, then either Harry
did not pass the test, or Ron did not pass the test either. And the way we frame that more
formally using logic is to say this. If it is not true that alpha
and beta, well, then either not alpha or not beta. The way I like to think
about this is that if you have a negation in front
of an and expression, you move the negation
inwards, so to speak. Moving the negation into each
of these individual sentences, and then flip the and into an or. So the negation moves inwards,
and the and flips into an or. So I go from not A and
B, to not A or not B. And there's actually a
reverse of De Morgan's law that goes in the other direction
for something like this. If I say, it is not true that
Harry or Ron passed the test, meaning neither of them passed the test,
well, then the conclusion I can draw is that Harry did not pass the
test, and Ron did not pass the test. So in this case, instead of turning
an and into an or, we're turning an or into an and. But the idea is the same. And this, again, is another
example of De Morgan's laws. And the way that works is that
if I have not A or B this time, the same logic's going to apply. I'm going to move the
negation inwards, and I'm going to flip, this time,
flip the or into an and. So if not A or B, meaning it is not
true that A or B, or alpha or beta, then I can say not alpha and not beta. Moving the negation inwards in
order to make that conclusion. So those are De Morgan's laws. And a couple of other inference rules
that are worth just taking a look at, one is the distributive
law that works this way. So if I have alpha and beta or gamma,
well, then much in the same way that you can use, in math,
use distributive laws to distribute operands, like
addition and multiplication, I can do a similar thing here. Where I can say if
alpha and beta or gamma, then I can say something like,
alpha and beta, or alpha and gamma. That I've been able to distribute this
and sign throughout this expression. So this is an example of
the distributive property or the distributive law, as applied
to logic in much the same way that you would distribute like a
multiplication over the addition of something, for example. This works the other way, too. So if, for example, I have
alpha or beta and gamma, I can distribute the or
throughout the expression. I can say alpha or beta,
and alpha or gamma. So the distributive law
works in that way, too. And it's helpful if I want to take an
or and move it into the expression. And we'll see an example soon of
why it is that we might actually care to do something like that. All right. So now we've seen a lot of
different inference rules. And the question now is how can we
use those inference rules to actually try and draw some conclusions? To actually try and prove
something about entailment, proving that given some
initial knowledge base, we would like to find some way
to prove that a query is true. Well, one way to think
about it is actually to think back to what we
talked about last time, when we talked about search problems. Recall again that search problems
have some sort of initial state. They have actions that you can
take from one state to another, as defined by a transition model that
tells you how to get from one state to another. We talked about testing
to see if you had a goal. And then some path cost function to see
how many steps did you have to take, or how costly was the
solution that you found. Now that we have these
inference rules that take some set of sentences
and propositional logic and get us some new set of
sentences in propositional logic, we can actually treat those
sentences, or those sets of sentences, as states inside of a search problem. So if we want to prove
that some query is true, prove that some logical
theorem is true, we can treat theorem proving as
a form of a search problem. I can say that we begin
in some initial state, where that initial state is the
knowledge base that I begin with. The set of all of the sentences
that I know to be true. What actions are available to me? Well, the actions are any
of the inference rules that I can apply at any given time. The transition model just tells me
after I apply the inference rule, here is the new set of all
of the knowledge that I have, which will be the old set of knowledge,
plus some additional inference that I've been able to draw,
much as in the same way we saw what we got when we
applied those inference rules and got some sort of conclusion. That conclusion gets added
to our knowledge base, and our transition
model will encode that. What is the goal test? Well, our goal test
is, you know, checking to see if we have proved the
statement we're trying to prove. If the thing we're trying to prove
is inside of our knowledge base. And the path cost function, the
thing we're trying to minimize, is maybe the number of inference
rules that we needed to use. The number of steps, so to
speak, inside of our proof. And so here we've been able to
apply the same types of ideas that we saw last time
with search problems, to something like trying to
prove something about knowledge by taking our knowledge and framing
it in terms that we can understand as a search problem,
with an initial state, with actions, with a transition model. So this shows a couple of things. One being how versatile
search problems are. That they can be the same types of
algorithms that we use to solve a maze, or figure out how to get from point A to
point B. Inside of driving directions, for example, can also be used
as a theorem proofing method. Of taking some sort of
starting knowledge base and trying to prove something
about that knowledge. So this, yet again, is a second
way, in addition to model checking, to try and prove that
certain statements are true. But it turns out there's yet another
way that we can try and apply inference, and we'll talk about this now,
which is not the only way, but certainly one of the most common. Which is known as resolution. And resolution is based
on another inference rule that we'll take a look at now. Quite a powerful
inference rule that will let us prove anything that can
be proven about a knowledge base. And it's based on this basic idea. Let's say I know that either
Ron is in the Great Hall, or Hermione is in the library. And let's say I also know that
Ron is not in the Great Hall. Based on those two pieces of
information, what can I conclude? Well, I could pretty reasonably conclude
that Hermione must be in the library. How do I know that? Well, it's because these two
statements, these two, what we'll call, complementary literals-- literals
that complement each other, they are opposites of each other-- seem to conflict with each other. This sentence tells us that
either Ron is in the Great Hall or Hermione is in the library. So if we know that Ron
is not in the Great Hall, that conflicts with this one, which
means Hermione must be in the library. And this we can frame as a more general
rule, known as the unit resolution rule. A rule that says that if we have P or
Q, and we also know not P, well, then from that we can reasonably conclude Q. That if P or Q are true, and
we know that P is not true, the only possibility is
for Q to then be true. And this, it turns out, is
quite a powerful inference rule in terms of what it can do,
in part because we can quickly start to generalize this rule. This Q right here doesn't need to
just be a single propositional symbol. It could be multiple, all chained
together in a single clause, as we'll call it. So if I had something like P or Q1,
or Q2, or Q3, so on and so forth, up until Qn, so I had n,
different, other variables, and I have not P, well, then what
happens when these two complement each other is that these two
clauses resolve, so to speak, to produce a new clause that is
just Q1 or Q2, all the way up to Qn. And in an or, the order of the arguments
in the or doesn't actually matter. The P doesn't need to
be the first thing. It could've been in the middle. But the idea here is that
if I have P in one clause, and not P and the other
clause, well, then I know that one of these
remaining things must be true. I've resolved them in order
to produce a new clause. But it turns out we can generalize
this idea even further, in fact, and display even more power that we
can have with this resolution rule. So let's take another example. Let's say, for instance, that I
know the same piece of information, that either Ron is in the Great
Hall or Hermione is in the library. And the second piece
of information I know is that Ron is not in the Great
Hall or Harry is sleeping. So it's not just a single
piece of information. I have two different clauses, and
we'll define clauses more precisely in just a moment. What do I know here? Well, again, for any propositional
symbol, like Ron is in the Great Hall, there are only two possibilities. Either Ron is in the Great Hall,
in which case, based on resolution, we know that Harry must be sleeping. Or Ron is not in the
Great Hall, in which case we know, based on the same rule,
that Hermione must be in the library. Based on those two
things in combination, I can say, based on these
two premises, that I can conclude that either Hermione is
in the library or Harry is sleeping. So again, because these two
conflict with each other, I know that one of
these two must be true. And you can take a closer look and
try and reason through that logic. Make sure you convince yourself
that you believe this conclusion. Stated more generally, we
can name this resolution rule by saying that if we
know P or Q is true, and we also know that
not P or R is true, we resolve these two clauses
together to get a new clause, Q or R. That either Q or R must be true. And again, much as in
the last case, Q and R don't need to just be single
propositional symbols. It could be multiple symbols. So if I had a rule that had a P or
Q1 or Q2 or Q3, so on and so forth, up until Qn, where n
is just some number. And likewise, I had not P or R1 or F2,
so on and so forth, up until Rm, m, where m, again, is
just some other number, I can resolve these two clauses together
to get one of these must be true. Q1 or Q2, up until Qn. Or R1 or R2, up until Rm. And this is just a generalization
of that same rule we saw before. Each of these things here
we're going to call a clause. Where a clause is formally defined
as a disjunction of literals. Where a disjunction means it's a bunch
of things that are connected with or. Disjunction means things
connected with or. Conjunction, meanwhile, is
things connected with and. And a literal is either a
propositional symbol or the opposite of a propositional symbol. So it's something like P
or Q, or not P or not Q, those are all propositional symbols,
or not of the propositional symbols, and we call those literals. So a clause is just something like this. P or Q or R, for example. Meanwhile, what this
gives us an ability to do is it gives us an ability to
turn logic, any logical sentence, into something called
conjunctive normal form. A conjunctive normal form
sentence is a logical sentence that is a conjunction of clauses. Recall again, conjunction means things
are connected to one another using and. And so a conjunction of
clauses means that it is an and of individual clauses,
each of which has in it. So something like this. A or B or C, and D or not E, and
F or G. Everything in parentheses is one clause. All of the clauses are connected
to each other using an and, and everything in the clause
is separated using an or. And this is just a standard form that
we can translate a logical sentence into that just makes it easy to
work with and easy to manipulate. And it turns out that we can
take any sentence in logic and turn it into
conjunctive normal form, just by applying some inference
rules and transformations to it. So we'll take a look at how
we can actually do that. So what is the process for
taking a logical formula and converting it into injunctive
normal form, otherwise known as CNF? Well, the process looks a
little something like this. We need to take all of
the symbols that are not part of conjunctive normal form-- the
biconditionals, and the implications, and so forth. And turn them into something
that is more closely like conjunctive normal form. So the first step will be
to eliminate biconditionals. Those if and only if double arrows. And we know how to
eliminate biconditionals because we saw there was an
inference rule to do just that. Anytime I have an expression,
like alpha, if and only if beta, I can turn that into alpha implies
beta, and beta implies alpha, based on that inference rule we saw before. Likewise, in addition to
eliminating biconditionals, I can eliminate implications as well. The if/then arrows. And I can do that using the same
inference rule we saw before, too. Taking alpha implies
beta, and turning that into not alpha or beta,
because that is logically equivalent to this first thing here. Then we can move nots inwards,
because we don't want nots on the outsides of our expressions. Conjunctive normal form requires
that its just clause and clause and clause and clause. Any nots need to be immediately
next to propositional symbols. But we can move those nots
around using De Morgan's laws. By taking something like, not A and
B, and turn it into not A or not B, for example, using de Morgan's
laws to manipulate that. And after that, all we'll be
left with are ands and ors, and those are easy to deal with. We can use the distributive law to
distribute the ors so that the ors end up on the inside of the
expression, so to speak, and the ands end up on the outside. So this is the general pattern
for how we'll take a formula and convert it into
conjunctive normal form. And let's now take a
look at an example of how we would do this, and explore
then, why it is that we would want to do something like this. Here's how we can do it. Let's take this formula, for example. P or Q implies R, and
I'd like to convert this into conjunctive normal form,
where it's all ands of clauses, and every clause is
a disjunctive clause. It's or is together. So what's the first thing I need to do? Well, this is an implication. So let me go ahead and
remove that implication. Using the implication inference
rule, I can turn P or Q into-- P or Q implies R, into not P or
Q or R. So that's the first step. I've gotten rid of the implication. And next, I can get rid of the not on
the outside of this expression, too. I can move the nots inwards so they're
closer to the literals themselves by using De Morgan's laws. And De Morgan's law says
that not P or Q is equivalent to not P and not Q. Again here,
just applying the inference rules that we've already seen in order
to translate these statements. And now I have two things
that are separated by an or, where this thing on
the inside is an and. What I'd really like is to move the
or so the ors are on the inside, because conjunctive normal form means
I need clause and clause and clause and clause. And so to do that, I can
use the distributive law. If I have not P and not Q or R, I can
distribute the or R to both of these to get not P or R, and not Q or
R using the distributive law. And this now, here at the bottom,
is in conjunctive normal form. It is a conjunction, an
and, of disjunctions, of clauses that just
are separated by ors. So this process can be used by any
formula to take a logical sentence and turn it into this
conjuncture normal form, where I have clause and clause and clause
and clause and clause, and so on. So why is this helpful? Why do we even care about
taking all these sentences and converting them into this form? It's because once they're in this
form where we have these clauses, these clauses are the inputs to the
resolution, inference rule that we saw a moment ago. That if I have two clauses where
there's something that conflicts, or something complementary
between those two clauses, I can resolve them to get a new
clause, to draw a new conclusion. And we call this process
inference by resolution. Using the resolution rule to
draw some sort of inference. And it's based on the same idea,
that if I have P or Q, this clause, and I have not P or R, that I can
resolve these two clauses together to get Q or R as the resulting clause. A new piece of information
that I didn't have before. Now, a couple of key points that are
worth noting about this before we talk about the actual algorithm. One thing is that, let's
imagine we have P or Q or S, and I also have not P or R or
S. The resolution rule says that because this P
conflicts with this not P, we would resolve to put everything
else together, to get Q or S or R or S. But it turns out that this double S is
redundant-- or S here and or S there. It doesn't change the
meaning of the sentence. So in resolution, when we
do this resolution process, we usually also do a
process known as factoring, where we take any duplicate variables
that show up and just eliminate them. So Q or S or R or S just becomes Q or R
or S. The S only needs to appear once. No need to include it multiple times. Now, one final question
worth considering is what happens if I try to
resolve P and not P together? If I know that P is true, and
I know that not P is true, well, resolution says I can merge
these clauses together and look at everything else. Well, in this case,
there is nothing else. So I'm left with what we
might call the empty clause. I'm left with nothing. And the empty clause is always false. The empty clause is equivalent
to just being false. And that's pretty reasonable. Because it's impossible for both P and
not P to both hold at the same time. P is either true or it's not true. Which means that if P is
true, then this must be false. And if this is true,
then this must be false. There's no way for both of
these to hold at the same time. So if ever I try and resolve
these two, it's a contradiction, and I'll end up getting this empty
clause, where the empty clause I can call equivalent to false. And this idea that if I resolve
these two contradictory terms I get the empty clause, this is the
basis for our inference by resolution algorithm. Here is how we're going
to perform inference by resolution at a very high level. We want to prove that our knowledge
base entails some query alpha. That based on the knowledge we
have, we can prove conclusively that alpha is going to be true. How are we going to do that? Well, in order to do
that, we're going to try to prove that if we know the
knowledge and not alpha, that that would be a contradiction. And this is a common technique in
computer science more generally. This idea of proving
something by contradiction. If I want to prove
that something is true, I can do so by first
assuming that it is false, and showing that it
would be contradictory. Showing that it leads
to some contradiction. And if the thing I'm trying to
prove, if when I assume it's false leads to a contradiction,
then it must be true. And that's the logical approach, or the
idea, behind a proof by contradiction. And that's what we're going to do here. We want to prove that
this query alpha is true, so we're going to assume
that it's not true. We're going to assume not alpha. And we're going to try and
prove that it's a contradiction. If we do get a
contradiction, well, then we know that our knowledge
entails the query alpha. If we don't get a contradiction,
there is no entail. This is this idea of a
proof by contradiction of assuming the opposite of
what you're trying to prove, and if you can demonstrate that that's a
contradiction, then what you're proving must be true. But more formally, how
do we actually do this? How do we check that
knowledge base and not alpha is going to lead to a contradiction? Well, here is where
resolution comes into play. To determine if our knowledge
base entails some query alpha, we're going to convert knowledge
base and not alpha to conjunction normal form. That form where we have a whole bunch
of clauses that are all anded together. And when we have these
individual clauses, now we can keep checking to
see if we can use resolution to produce a new clause. We can take any pair
of clauses and check. Is there some literal that is
the opposite of each other, or complementary to each
other, in both of them? For example, I have a P in one
clause, and a not P in another clause. Or an R in one clause, and
a not R in another clause. If ever I have that
situation, where once I convert to conjunctive normal form
and I have a whole bunch of clauses, I see two clauses that I can resolve to
produce a new clause, then I'll do so. This process occurs in a loop. I'm going to keep checking to
see if I can use resolution to produce a new clause, and keep using
those new clauses to try to generate more new clauses after that. Now, it just so may
happen that eventually, we may produce the empty clause. The clause we were talking about before. If I resolve P and not P together,
that produces the empty clause. And the empty clause
we know to be false. Because we know that there's
no way for both P and not P to both simultaneously be true. So if ever we produce the empty
clause, then we have a contradiction. And if we have a contradiction,
that's exactly what we were trying to do in
a proof by contradiction. If we have a contradiction, then
we know that our knowledge base must entail this query alpha. We know that alpha must be true. And it turns out-- and we
won't go into the proof here-- but you can show that, otherwise, if
you don't produce the empty clause, then there is no entailment. If we run into a situation where
there are no more new clauses to add, we've done all the
resolution that we can do, and yet we still haven't
produced the empty clause, then there is no
entailment in this case. And this now is the
resolution algorithm. And it's very abstract
looking, especially this idea of what does that even
mean to have the empty clause. So let's take a look at an example. Actually try and prove some
entailment by using this inference by resolution process. So here's our question. We have this knowledge base. Here is the knowledge that we know. A or B, and not B or C, and not C.
And we want to know if all of this entails A. So this is our knowledge base
here, this whole log thing. And our query alpha is just
this propositional symbol A. So what do we do? Well, first we want to
prove by contradiction. So we want to first
assume that A is false, and see if that leads to
some sort of contradiction. So here is what we're
going to start with. A or B, and not B or C, and not
C. This is our knowledge base. And we're going to
assume not A. We're going to assume that the thing we're
trying to prove is, in fact, false. And so this is now in
conjunctive normal form, and I have four different clauses. I have A or B. I have not B or C.
I have not C. And I have not A. And now, I can begin to just pick two
clauses that I can resolve and apply the resolution rule to them. And so looking at these
four clauses I see, all right, these two clauses
are ones I can resolve. I can resolve them because there
are complementary literals that show up in them. There's a C here, and a not C here. So just looking at these two clauses,
if I know that not B or C is true, and I know that C is
not true, well, then I can resolve these two clauses to say,
all right, not B. That must be true. I can generate this new clause
as a new piece of information that I now know to be true. And all right, now I
can repeat this process. Do the process again. Can I use resolution again
to get some new conclusion? Well, it turns out I can. I can use that new clause I just
generated, along with this one here. There are complementary literals. This B is complementary to, or
conflicts with this not B over here. And so if I know that A or B is
true, and I know that B is not true, well, then the only remaining
possibility is that A must be true. So now we have A. That is a new clause
that I've been able to generate. And now I can do this one
more time, and looking for two clauses that can be resolved. And you might programmatically
do this by just looping over all possible pairs
of clauses and checking for complementary literals in each. And here I can say, all
right, I found two clauses. Not A and A that
conflict with each other. And when I resolve these
two together, well, this is the same as when we were
resolving P and not P from before. When I resolve these two clauses
together, I get rid of the As, and I'm left with the empty clause. And the empty clause we know
to be false, which means we have a contradiction. Which means we can safely
say, that this whole knowledge base does entail A. That
if this sentence is true, that we know that A,
for sure, is also true. So this now, using
inference by resolution, is an entirely different
way to take some statement and try and prove that
it is, in fact, true. Instead of enumerating
all of the possible worlds that we might be in in order to
try to figure out in which case is the knowledge base true, and
in which case is our query true. Instead we use this
resolution algorithm to say, let's keep trying to figure out
what conclusions we can draw and see if we reach a contradiction. And if we reach a
contradiction, then that tells us something about whether our
knowledge actually entails the query or not. And it turns out there are
many different algorithms that can be used for inference. What we've just looked at here
are just a couple of them. And, in fact, all of this is just
based on one particular type of logic. It's based on propositional logic,
where we have these individual symbols and we connect them using and,
and or, and not, and implies, and biconditionals. But propositional logic is not the
only kind of logic that exists. And in fact, we see that
there are limitations that exist in propositional
logic, especially as we saw in examples like
with the Mastermind example, or with the example
with the logic puzzle where we had different Hogwart's has
people that belong to different houses, and we were trying to figure out
who belonged to which houses. There were a lot of different
propositional symbols that we needed in order to
represent some fairly basic ideas. So now as a final topic
that we'll take a look at, just before we end class today,
is one final type of logic, different from
propositional logic, known as first order logic,
which is a little bit more powerful than
propositional logic, and is going to make it easier for us to
express certain types of ideas. In propositional logic, if
we think back to that puzzle with the people and
the Hogwart's houses, we had a whole bunch of
symbols, and every symbol could only be true or false. We had a symbol for Minerva
Gryffindor, which was either true if Minerva was in Gryffindor,
and false otherwise. And likewise, for Minerva
Hufflepuff, and Minerva Ravenclaw, and Minerva Slytherin, and so forth. But this was starting
to get quite redundant. That we wanted some way
to be able to express that there's a relationship between
these propositional symbols. That Minerva shows up in all of them. And also, I would have
liked to have not have had so many different
symbols to represent what really was a fairly
straightforward problem. So first order logic will
give us a different way of trying to deal with
this idea by giving us two different types of symbols. We're going to have constant symbols
that are going to represent objects, like people or houses. And then predicate
symbols, which you can think of as relations or functions,
that take an input and evaluate them to, like, true or false, for example. That tell us whether or not
some property of some constant, or some pair of constants, or
multiple constants, actually holds. So we'll see an example
of that in just a moment. But for now, in this same problem,
our constant symbols might be objects. Things like people or houses. So Minerva, Pomona, Horace, Gildaroy. Those are all constant symbols. As are my four houses-- Gryffindor, Hufflepuff,
Ravenclaw, and Slytherin. Predicates, meanwhile,
these predicate symbols are going to be properties
that might hold true or false of these individual constants. So person might hold true of Minerva,
but it would be false for Gryffindor, because Gryffindor is not a person. And house is going to
hold true for Ravenclaw, but it's not going to hold
true for Horace, for example, because Horace is a person. And belongs, meanwhile, is going
to be some relation that is going to relate people to their houses. And it's going to only tell me when
someone belongs to a house or does not. So let's take a look at some examples
of what a sentence in first order logic might actually look like. A sentence might look
like something like this. Person Minerva, with
Minerva in parentheses. And person being a predicate symbol,
Minerva being a constant symbol. This sentence in first order logic
effectively means Minerva is a person, or the person property
applies to the Minerva object. So if I want to say something
like Minerva is a person, here is how I express that
idea using first order logic. Meanwhile, I can say something
like house Gryffindor to, likewise, express the idea
that Gryffindor is a house. I can do that this way. And all of the same logical connectives
that we saw in propositional logic, those are going to work here, too. So and, or, implication,
biconditional, not. In fact, I can use not to say
something like, not house Minerva. And this sentence in first order
logic means something like Minerva is not a house. It is not true that the house
property applies to Minerva. Meanwhile, in addition to some
of these predicate symbols that just take a single argument,
some of our predicate symbols are going to express binary relations. Relations between two of its arguments. So I could say something like,
belongs to, and then two inputs, Minerva and Gryffindor
to express the idea that Minerva belongs to Gryffindor. And so now here's the key difference,
or one of the key differences, between this and propositional logic. In propositional logic, I needed
one symbol for Minerva Gryffindor, and one symbol for Minerva
Hufflepuff, and one symbol for all the other people's
Gryffindor and Hufflepuff variables. In this case, I just need one
symbol for each of my people, and one symbol for each
of my houses, and then I can express, as a predicate,
something like, belongs to, and say, belongs to Minerva
Gryffindor to express the idea that Minerva belongs
to Gryffindor house. So already we can see
that first order logic is quite expressive in being able to
express these sorts of sentences using the existing constant
symbols and predicates that already exist, while minimizing
the number of new symbols that I need to create. I can just use eight symbols
for people, for houses, instead of 16 symbols for every
possible combination of each. But first order logic gives us
a couple of additional features that we can use to express
even more complex ideas. And these additional features are
generally known as quantifiers. And there are two main
quantifiers in first order logic. The first of which is
universal quantification. Universal quantification
lets me express an idea, like something is going to be
true for all values of a variable. Like for all values of x, some
statement is going to hold true. So what might a sentence in
universal quantification look like? Well, we're going to use this
upside-down A to mean for all. So upside-down Ax means for all
values of x, where x is any object, this is going to hold true. Belongs to x Gryffindor implies
not belongs to x Hufflepuff. So let's try and parse this out. This means that for all values
of x, If this holds true, if x belongs to Gryffindor,
then this does not hold true. X does not belong to Hufflepuff. So translated into
English, this sentence is saying something like, for all
objects x, if x belongs to Gryffindor, then x does not belong to
Hufflepuff, for example. Or phrased even more simply, anyone
in Gryffindor is not a Hufflepuff. A simplified way of
saying the same thing. So this universal quantification
lets us express an idea, like something is going to hold true
for all values of a particular variable. In addition to universal
quantification, though, we also have existential quantification. Whereas universal quantification
said that something is going to be true for
all values of variable, existential quantification
says that some expression is going to be true for
some value of a variable. At least one value of the variable. So let's take a look at
a sample sentence using existential quantification. One such sentence looks like this. There exists an x-- this
backwards E stands for exists. And here we're saying there
exists an x, such that house x and belongs to Minerva x. In other words, there exists some
object x, where x is a house, and Minerva belongs to x. Or phrased a little more succinctly
in English, you're just saying, Minerva belongs to a house. There's some object that is a house,
and Minerva belongs to a house. And combining this universal
and existential quantification, we can create far more
sophisticated logical statements than we were able to just
using propositional logic. I could combine these to
say something like this. For all x, person x implies there
exists a y, such that house y and belongs to xy. So a lot of stuff going on there. A lot of symbols. Let's try and parse it out and
just understand what it's saying. Here we're saying that for all values of
x, if x is a person, then this is true. So in other words, I'm
saying for all people, and we call that person x, this
statement is going to be true. What statement is true of all people? Well, there exists a y that is the
house, so there exists some house, and x belongs to y. In other words I'm saying,
that for all people out there, there exists some house, such that x,
the person, belongs to y, the house. So say it more succinctly, I'm saying
that every person belongs to a house. That for all x, if x is a person, then
there exists a house that x belongs to. And so we can now express a lot more
powerful ideas using this idea now of first order logic. And it turns out there are many
other kinds of logic out there. There's second order logic and other
higher order logic, each of which allows us to express more
and more complex ideas. But all of it, in this case, is really
in pursuit of the same goal, which is the representation of knowledge. We want our AI agents to be
able to know information, to represent that
information, whether that's using propositional logic, or first
order logic, or some other logic. And then be able to
reason based on that. To be able to draw conclusions. Make inferences. Figure out whether there's some
sort of entailment relationship, as by using some sort
of inference algorithm. Something like inference by
resolution, or model checking, or any number of these
other algorithms that we can use in order to take
information that we know and translate it to
additional conclusions. So all of this has
helped us to create AI that is able to represent
information about what it knows and what it doesn't know. Next time though, we'll
take a look at how we can make our AI even more powerful
by not just encoding information that we know for sure to be true,
and not to be true, but also, to take a look at uncertainty. To look at what happens if AI thinks
that something might be probable, or maybe not very probable, or
somewhere in between those two extremes, all in the pursuit of trying to build
our intelligent systems to be even more intelligent. We'll see you next time.