ELIZA again: how the program works

But we have already seen another way of going about things, that taken by ELIZA. Recall that the third surprising reaction to ELIZA was the spread of a belief that it demonstrated a general solution to the problem of computer understanding of natural language. Why should that be surprising? Just how real is that understanding? It is time we looked at just how ELIZA works.

Many of you may have used an ELIZA program, so you may have some intuitions as to how ELIZA goes about generating responses to input sentences. The two keys concepts I shall examine are 'keywords' and 'sentence patterns':

8.1. Keyword search

[Download simple_eliza.pro]

The simplest mechanism that ELIZA might use is keyword search. The program scans the sentence for a word that is a specific instance of some more general category or a member of a set. The word 'mother', for example, is a member of the set which contains 'father', 'brother', and so on, and which we might label 'family'. Similarly, 'hate', 'dislike', and 'detest' can be grouped together as exemples of 'strong feelings'. In the first case, it is often appropriate to respond with some thing like "Tell me more about you family", in the second case "Do strong feelings disturb you?"

Let's have a first attempt at writing an ELIZA program on this basis. We'll begin by writing a predicate keyword/2 which will consist of a list of thematically grouped keywords followed by an appropriate response:

keyword([mother, father, sister, brother, son, daughter],
'Tell me more about your family').
keyword([depressed, guilty, advice, unhappy],
'Perhaps you need to get away for a while?').
keyword([need, desire, hates, hate, cry, love],
'Do strong feelings disturb you?').
keyword([money, broke, cash, rent, cost],
'All except the rich have financial problems nowadays').

We'll need to check whether a word in the input string is a member of a list of keywords, so we'll need the member/2 predicate in the program:

member(X, [X|_]).
member(X, [_|Y]):-
member(X, Y).

Now we'll need a procedure that, given an input string, will check whether any word in the string also occurs in one of the lists of keywords. The predicate respond/1 iterates through the 'keyword' lists, checking for each word in the input string (the first call to member) whether it is a keyword (second call to member). If it is, then ELIZA retrieves the corresponding response and uses that as the new question to the user (ask/2) and the dialog begins anew. If there is no match of keyword, ELIZA simply responds with a default 'Please go on'. The special case is when the user wants to quit, in which case she will simply type 'bye' and the program halts.

respond([bye]):-
write('bye!'), nl, nl.
respond(Input):-
keyword(WList, Response),
member(W, Input),
member(W, WList),
ask(Response, NewInput),
respond(NewInput).
respond(W):-
ask('Please go on', Response),
respond(Response).

Finally, we need a top-level call on the program. simple_eliza/1 runs the program by first asking a question and soliciting input from the user; the user's response then is passed on as input to the procedure respond:

simple_eliza:-
ask('Hi, my name is Eliza. Tell me about your problems', Input),
respond(Input).

(The predicate ask/2 is defined in an appendix to this tutorial. Some implementations of Prolog may have equivalent built-in I/O predicates.)

Below is a sample run of the above program:

?- simple_eliza.
Hi, my name is Eliza. Tell me about your problems
> my father hates me
Tell me more about your family
> they make me feel guilty
Perhaps you need to get away for a while?
> do you mean take a holiday?
I am not sure I understood you
> i don't have the money to get away
All except the rich have financial problems nowadays
> i hate being poor
Do strong feelings disturb you?
> very much so
Please go on
> bye
bye!