AH, GOFAI! It seems to be a dying art these days, I look through AI journals and it seems everyone’s caught up in the hype of neural-nets, SVMs, and other such ‘curve-fitting’ methods. The days of “Good Old Fashioned AI” seem to be numbered, except they’re not!
So why the hype about modern methods? They pretty much out perform GOFAI across the board on accuracy, recall, precision etc. They’re cheaper to make in some ways: no need to try and extract knowledge from your domain experts. But they’re more expensive in some ways too: they need lots of data. Modern methods are achieving great things, like playing Go and identifying things in images or video.
So why bother with GOFAI? Well, only GOFAI can explain itself. When you’re just playing a game of Go or playing with finding cats in photos, it doesn’t matter too much why the computer decided what it did. But, when it’s a safety critical system, or diagnostic, or legal, or a myriad of other reasons, you really want to know why the computer made those decisions. With GOFAI you know what the computer has learnt, you can inspect it’s learning and make adjustments. This is particularly important in cases of bias.
Like all solutions, neither approach is the answer to all problems and a middle path combining the two will likely prove best in the end. In this post we’ll look at how to do a simple expert system to identify fruits, in Prolog.
WE STILL need data, but this time it’s from an expert. Ok, not such an expert, I wrote this knowledge base. Feel free to add more fruit, make sure each one can be uniquely identified.
% Data: fruit(X) :- attributes(Y) fruit(banana) :- colour(yellow), shape(crescent). fruit(apple) :- (colour(green); colour(red)), shape(sphere), stem(yes). fruit(lemon) :- colour(yellow), (shape(sphere);shape('tapered sphere')), acidic(yes). fruit(lime) :- colour(green), shape(sphere), acidic(yes). fruit(pear) :- colour(green), shape('tapered sphere'). fruit(plum) :- colour(purple), shape(sphere), stone(yes). fruit(grape) :- (colour(purple);colour(green)), shape(sphere). fruit(orange) :- colour(orange), shape(sphere). fruit(satsuma) :- colour(orange), shape('flat sphere'). fruit(peach) :- colour(peach). fruit(rhubarb) :- (colour(red); colour(green)), shape(stick). fruit(cherry) :- colour(red), shape(sphere), stem(yes), stone(yes).
The Expert System
IN THIS system the ‘expert’ needs to ask the user about the fruit, when it can identify the correct one, it will. We have two types of values, boolean ones that can be ‘yes’, and ones that can be from a set of values, like colour. For boolean values we can ask if something is correct, for the set of values we’ll make a menu.
% Asks colour(X) :- menuask(colour, X, [red, orange, yellow, green, purple, peach]). shape(X) :- menuask(shape, X, [sphere, crescent, 'tapered sphere', 'flat sphere', stick]). acidic(X) :- ask(acidic, X). stem(X) :- ask(stem, X). stone(X) :- ask(stone, X).
Now we need to define
menuask/3. I’m going to intersperse them both so we can do the predicates by job, so I’ll need to tell Prolog the definitions aren’t grouped by using the
discontiguous predicate. I’m also going to get Prolog to remember answers using a
known/3 predicate, so I’ll need to tell Prolog this will be changing in runtime with the
So first we want Prolog to check if it has a fact in
known/3. Then we want it to fail if it doesn’t have that fact. Then we want it to fail if it has that same attribute known with a different value, this catches cases where we’ve told it the colour is red and it is asking if the colour is yellow. Finally, if we’ve not caught anything from
known/3, we must not know it, so we ask.
:- dynamic(known/3). :- discontiguous menuask/3. :- discontiguous ask/2. % Remember what I've been told is correct ask(Attr, Val) :- known(yes, Attr, Val), !. menuask(Attr, Val, _) :- known(yes, Attr, Val), !. % Remember what I've been told is wrong ask(Attr, Val) :- known(_, Attr, Val), !, fail. menuask(Attr, Val, _) :- known(_, Attr, Val), !, fail. % Remember when I've been told an attribute has a different value ask(Attr, Val) :- known(yes, Attr, V), V \== Val, !, fail. menuask(Attr, Val, _) :- known(yes, Attr, V), V \== Val, !, fail. % % I don't know this, better ask! ask(Attr, Val) :- write(Attr:Val), write('? '), read(Ans), asserta(known(Ans, Attr, Val)), Ans == yes. menuask(Attr, Val, List) :- write('What is the value for '), write(Attr), write('? '), nl, write(List), nl, read(Ans), check_val(Ans, Attr, Val, List), asserta(known(yes, Attr, Ans)), Ans == Val. check_val(Ans, _, _, List) :- member(Ans, List), !. check_val(Ans, Attr, Val, List) :- write(Ans), write(' is not a known answer, please try again.'), nl, menuask(Attr, Val, List).
Just to make our user’s life easier, we’ll make a starting predicate.
go :- fruit(Fruit), write('The fruit is '), write(Fruit), nl.
Now we can call this in Prolog, and our expert will get to work!
?- go. What is the value for colour? [red, orange, yellow, green, purple, peach] green What is the value for shape? [sphere, crescent, tapered sphere, flat sphere, stick] stick The fruit is rhubarb true
The Code And The Book
CREDIT WHERE credit is due, check out the book “Expert Systems in Prolog” by Dennis Merritt for much more on this topic and a method to separate the knowledge base from the expert system. SWI-Prolog have an fantastic online Prolog IDE, which means you can run this code online and identify that fruit.
IT’S CLEAR the greatest investment in such an expert system will be in the knowledge/data base. It takes quite some thought even on a simple (?!) topic like fruit to be able to identify them. It also relies on experts actually having the knowledge and being able to describe it.
I argued earlier that the great benefit of this method is that it can explain why it made a decision, but in this code no explanation is provided. It won’t be too hard to add in a print out, but in this example I wanted to keep the code as short and simple as possible. If you want an explained version, you can always run
trace, go. for a really detailed breakdown.