ELIZA 정신병환자 진단 프로그램

 

인공지능의 원리와 실무사례 : 이광형. 홍유식. 김인택. 진현수. 민준영. 주영훈 공저, 동영출판사, 1998, Page 109~124 

이 프로그램은 정신병환자가 의사가 없이도 COMPUTER CRT를 이용해서 서로 간단한 대화를 주고받을 수 있는 프로그램이다. 컴퓨터에서 HOW ARE YOU THIS BEAUTIFUL DAY?의 내용을 의사가 물어 보듯이 프로그램에서 언어가 자동 생성되고 환자가 이 내용에 해당하는 대답 :  I DON'T KNOW, I FEEL BAD.을 COMPUTER KEYBOARD로 TYPING하면 프로그램에서 TRANSE와 FRONT TOKEN의 기능으로 다음과 같은 언어가 자동 생성되어 CRT에 DISPLAY될 것이다.

HOW DO YOU KNOW THAT?

그러면 이와 같은 기능이 어떻게 되는지를 알아보자. 우선 우리가 사용하는 언어도 알고 보면 DO YOU HAVE A BOOK?의 대답으로는 YES, I HAVE A BOOK. ARE YOU A BOY?의 대답으로는 YES, I AM A BOY.와 같이 될 것이다. 즉 우리는 의문문의 주어와 동사는 긍정문으로 대답될 때에 YOU를 I로 바꾸고 동사 ARE를 AM으로 바꾼다. 이러한 것은 TRANS의 기능을 이용해서 적절한 단어에 대응하게끔 바꿀 수 있다.

        char *trans[ ] = {
                "you", "Let's not talk about me.",
                "think", "Why do you think that?",
                "hate", "So you hate something - tell me more.",

  그리고 목적어는 그대로 반복되어 긍정문으로 사용된다. 이런 원리를 컴푸터에 이용하기 위해서 한 문장을 HEAD와 TAIL로 나눈 후에 int head=0;/* head of topics queue*/ int tail=0;
 /* tail of topics queue */ 프로그램은 환자가 입력한 응답을 단순히 RETURN을 치거나 길이가 VERYSHORT개의 #define SHORT 10 #define VERYSHORT 3 문자보다 짧은 반응을 넣으면 RESPOND( )의 호출함으로써 이전의 주제로 돌아가려고 한다.

        if (strlen(s)<VERYSHORT && strcmp(s, "bye")) {
          else{
             if (!*response[res]) res=0;/* start over again */
             printf("%s\n", response[res++]);
          }
          return;
        }

  TOPICS 큐에 주제가 있으면 FIND_TOPIC( )는 참을 리턴하고 주제를 배열 T에 넣을 것이다.  만약 이전의 주제가 없으면 RESPOND( )는 RESPONSE 데이터베이스로부터 전에 있던 반응 (STOCK RESPONSE)을 선택한다. 환자가 SHORT문자수보다 더 긴 반응을 넣으면 RESPOND( )는 대답을 TOPICS 큐에 놓는다.

          if (strlen(s)<VERYSHORT && strcmp(s, "bye")) {
                  if (find_topic(t))  {
                    printf("You just said : ");
                    printf("%s \n", t);
                    printf("tell me more.\n");
                    }

  이 체크는 의사가 매우 짧은 대답을 주제로 잘못하는 것을 막기 위해서 필요하며 RESPOND( )는 TRANS 데이터베이스에 있는 어떤 단어에 대해서도 환자의 반응을 자세히 조사한 후에 RESPOND( )가 하나를 발견하면 적당한 반응을 만들고 문장의 끝에서 어떤 단어도 데이터베이스에 있는 것과 일치하지 않으면 의사는 단순히 TELL ME MORE라고 말한다. 함수 respond( )는 "my"를 "you"하는 것처럼 몇 단어의 내용을 바꾸기 위해서 trans 데이터베이스를 사용한다. 프로그램이 trans 데이터베이스에서 단어를 찾지못하면 현재의 단어를 단순히 다시 프린트한다. 환자가 return을 치면 respond( )는 반응 데이터베이스로부터 이미 있는 반응(stock response)을 사용할 것이다. 프로그램은 get_token( )함수의 약간 변형된 버전을 사용하여 환자의 반응을 분석한다.

        get_token( );
          loc=lookup(token);
          if (loc!=-1) {
             printf("%s\n", trans[loc+1]);
             return;
          }
        }  while(*token);

  즉 환자의 반응을 저장할 큐를 만들고 TRANS 데이터베이스는 키워드와 반응을 간직할 것이다. 즉 프로그램이 키워드를 인식할 때마다 적당한 반응을 나타낼 것이다.  2차원 문자배열 TOPICS는 환자의 반응에 대한 환형큐를 유지하고 프로그램은 TOPICS를 인덱스로 하고 큐를 만들기 위해서 HEAD와 TAIL 변수들을 사용한다.  MAX는 큐를 설정한 임의의 값이며 프로그램은 환자가 답을 넣으면 나중에 행위의 어떤 과정이 취해질 것인지를 결정하기 위해서 SHORT와 VERYSHORT를 사용한다. 프로그램은 환자가 입력한 응답을 단순히 RETURN을 치거나 길이가 VERYSHORT개의 문자보다 짧은 반응을 넣으면 REPOND( )의 호출함으로써 이전의 주제로 돌아가려고 한다. TOPICS 큐에 주제가 있으면 FIND_TOPIC( )는 참을 리턴하고 주제를 배열 T에 넣을 것이다. 만약 이전의 주제가 없으면 RESPOND( )는 RESPONSE 데이터베이스로부터 전에 있던 반응(STOCK RESPONSE)을 선택한다.
  환자가 SHORT문자수보다 더 긴 반응을 넣으면 RESPOND( )는 대답을 TOPICS 큐에 놓는다. 이 체크는 의사가 매우 짧은 대답을 주제로 잘못하는 것을 막기 위해서 필요하며, RESPOND( )는 TRANS 데이터베이스에 있는 어떤 단어에 대해서도 환자의 반응을 자세히 조사한 후에 RESPOND( )가 하나를 발견하면 적당한 반응을 만들고 문장의 끝에서 어떤 단어도 데이터베이스에 있는 것과 일치하지 않으면 의사는 단순히 TELL ME MORE라고 말한다. 함수 respond( )는 "my"를 "your"하는 것처럼 몇 단어의 내용을 바꾸기 위해서 trans 데이터베이스를 사용한다.
  프로그램이 trans 데이터베이스에서 단어를 찾지 못하면 현재의 단어를 단순히 다시 프린트 한다. 환자가 return을 치면 respond( )는 반응 데이터베이스로부터 이미 있는 반응(stock response)을 사용할 것이다. 프로그램은 get_token( )함수의 약간 변형된 버전을 사용하여 환자의 반응을 분석한다. 즉 환자의 반응을 저장할 큐를 만들고, TRANS 데이터베이스는 키워드와 반응을 간직할 것이다. 즉 프로그램이 키워드를 인식할 때마다 적당한 반응을 나타낼 것이다.  2차원 문자배열 TOPICS는 환자의 반응에 대한 환형큐를 유지하고 프로그램은 TOPICS를 인덱스로 하고 큐를 만들기 위해서 HEAD와 TAIL 변수들을 사용한다. MAX는 큐를 설정한 임의의 값이며 프로그램은 환자가 답을 넣으면 나중에 행위의 어떤 과정이 취해질 것인지를 결정하기 위해서 SHORT와 VERYSHORT를 사용한다.  

실행결과 

   컴퓨터 스크린  :   HOW ARE YOU THIS BEAUTIFUL DAY?
   사용자             :   I DON'T KNOW. I FEEL BAD
   컴퓨터 스크린  :   HOW DO YOU KNOW THAT?
   사용자             :   I FEEL LIKE I COULD KILL.
   컴퓨터 스크린  :  IT IS WRONG TO KILL
   사용자             :   I NEVER SEEM TO WIN
 

인공지능언어를 사용한 경우 

   환자가 RESPONSE한 내용 MY, YOUR로 하기 위해서 TRANSLATE를 사용한다.
   ASSERT(translate("UNHAPPY", "WHY ARE YOU UNHAPPY?")),
   ASSERT(translate("THINK", "WHY DO YOU THINK THAT?")), 
   ASSERT(translate("YOU", "LET'S NOT TALK ABOUT ME."))

  만일 RESPOND가 TRANSE  데이터베이스에서 단어를 발견하지 못하면 DOCTOR는 RESPONSE 데이터베이스에 저장된 응답을 이용하여 환자가 입력한 응답에  ASSERT의 기능을 이용하여 데이터베이스의 앞이나 뒤에 절을 추가시킨다.

     PATIENT(S)  :-
          ASSERT(patresponses(S)),
          ASSERT(temp(S)),
     PURGE  :- 
          RETRACT(translate(_, _)),
     FAIL.

  ASSERTA(X)와 ASSERTZ(X) 는 똑같이 데이터베이스에 절을 추가하지만  ASSERTA(X)..... 데이터베이스의 제일 앞에 절을 추가하고 ASSERTZ(X)...... 데이터베이스의 제일 뒤에 절을 추가하고 RETRACT(X)..... 데이터베이스에서 불필요한 절들을 추가한다. 또 프로그램은 DOCTOR가 환자의 응답을 처리할 수 있을 때까지 TEMP 데이터베이스에 저장한다.

      TEMP(S),
      S=quit,
      PURGE.


  환자가 단지 RETURN 키를 친다면 DOCTOR의 첫 번째 라인은 앞의 주제에 관한 내용으로 돌아가려고 한다. TOPICS 데이터베이스에 앞의 주제에 관한 내용이 없다면 프로그램은 RESPONSES 데이터베이스에 저장된 응답 중에서 적합한 것을 환자가 한 문장을 선택하면 RESPOND가 실행되며 실행에 성공하기 위해서는 환자가 입력한 문장에서 키워드를 발견해야 한다. 키워드를 발견하면 프로그램은 키워드와 연결된 응답을 하고 발견하지 못하면 DOCTOR의 세 번째 절을 이용해서 문장에서 다음 키워드를 찾으려고 한다.


    PARETRACT(topics(R)),
    R<>"", WRITE("YOU JUST SAID", ""),
    WRITE(R, ""), NL,
    WRITE("TELL ME MORE..."), NL.
    RESPOND("") :=FAIL.
    RESPOND(S) :-
    FRONTTOKEN(S, T, _),
    TRANSLATE(T, T2),
    WRITE(T2, " ").
    RESPOND(S) :-
    FRONTTOKEN(S, _, S2),
    RESPOND(S2).TIENT(S) :-

  문장 끝에 도달하고도 키워드를 찾지 못하면 술어전체는 실행에 실패한다. 이때에는 DOCTOR의 마지막 절이 수행되며 마지막 절은 첫 번째 절과 동일하다.


    /* ELIZA 정신병환자 진료 프로그램 */
    /* 비인공지능언어 TURBO C를 사용한 예 */

    /* Doctor #3 - optimistic and easily annoyed. */
     #define MAX 100
     #define SHORT 10
     #define VERYSHORT 3
     
     char *response[] = {
        "How are you this beautiful day?",
        "Did you have a happy childhood?",
        "Did you hate your father?",
        "I'm not sure I understand.",
        ""
     };

     char *trans[] = {
        "you", "Let's not talk about me.",
        "think", "Why do you think that?",
        "hate", "So you hate something - tell me more.",
        :what", "Why do you ask?",
        "want", "Why do you want that?",
        "need", "We all need many things - is this special?",
        "why", "Remember, therapy is good for you.",
        "know", "How do you know that?",
        "bye", "Your bill will be mailed to you.",
        "murder", "I don't like killing.",
        "kill", "It is wrong to kill.",
        "jerk", "Don't ever call me a jerk!",
        "can't", "Don't be negative - be positive.",
        "failure", "Strive for success.",
        "never", "Don't be negative - be positive.",
        "unhappy", "Why are you unhappy?",
        ""
     };

     char topics[MAX][80];/* holds old topics */
     char token[80];
     char *p_pos;
     int res=0;/* index into response array */
     int head=0;/* head of topics queue */
     int tail=0;/* tail of topics queue */

     main()
     {
        char s[80];
        printf("%s\n", response[res++]);
        do {
             printf(" : ");
             p_pos=s;
             gets(s);
             respond(s);
        }  while(strcmp(s, "bye"));
     }

     /* create the doctor's responses */
     respond(s)
     char *s;
     {
        char t[80];
        int loc;

        if (strlen(s) < VERYSHORT && strcmp(s, "bye")) {
           if (find_topic(t)) {
                printf("You just said :");
                printf("%s \n", t);
                printf("tell me more.\n");
           }
           else {
             if (!*response[res]) res=0;/* start over again */
             printf("%s\n", response[res++]);
           }
           return;
        }
        if (in_topics(s)) {
           printf("Stop repeating yourself!\n");
           return;
        }

        if (strlen(s) > SHORT) assert_topic(s);
        do {
           get_token();
           loc=lookup(token);
           if (loc!=-1) {
                printf("%s\n", trans[loc+1]);
                return;
           }
        } while(*token);
        /* comment of last resort */
        printf("Tell me more..., \n");
     }

     /* lookup a keyword in translation table */
     lookup(token)
     char *token;
     {
        int t;
        
        t=0;
        while(*trans[t]) {
           if (!strcmp(trans[t], token)) return t;
           t++;
        }
        return -1;
     }
     /* place a topic into the topics database */
     assert_topic(t)
     char *t;
     {
        if (head==MAX) head=0; /* wrap around */
        strcpy(topics[head], t);
        head++;
     }
     /* retrieve a topic */
     find_topic(t)
     char *t;
     {
        if (tail!=head) {
           strcpy(t, topics[tail]);
           tail++;
           /* wrap around if necessary */
           if (tail==MAX) tail=0;
           return 1;
        }
        return 0;
     }

     /* see if in topics queue */
     in_topics(s)
     char *s;
     {
        int t;
        for (t=0; t<MAX; t++)
           if (!strcmp(s, topics[t])) return 1;
        return 0;
     }
     /* return a token from the input stream */
     get_token()
     {
        char *p;
        p=token;
        /* skip spaces */
        while (*p_pos==' ') p_pos++;
        if (*p_pos=='\0') {  /* is end of input */
           *p++='\0';
           return;  
        }
        if (is_in(*p_pos, ".!?")) {
           *p=*p_pos;
           p++, p_pos++;
           *p='\0';
           return;
        }
        /* read word until */
        while(*p_pos!=' ' && !is_in(*p_pos, "., ;?!") && *p_pos) {
           *p=tolower(*p_pos++);
           p++;
        }
        *p='\0';
     }
     is_in(c, s)
     char c, *s;
     {
        while(*s) {
           if(c==*s) return 1;
           s++;
        }
        return 0;
     }

    /* ELIZA 정신병 혼자 진료 프로그램 */
    /* 인공지능언어 TURBO PROLOG를 사용한 예 */
    /* AN IMPROVED DOCTOR PROGRAM */
     diagnostics
     domains
     list = symbol*
     number = integer
     database
      RESPONSES(STRING)
      TEMP(STRING)
      TRANSLATE(SYMBOL, SYMBOL)
      TOPICS(STRING)
      PATRESPONSES(STRING)

     PREDICATES
      CONVERSE
      PATIENT(STRING)
      DOCTOR(STRING)
      RESPOND(STRING)
      purge

    GOAL

     makewindow(1, 7, 4, " ", 0, 0, 25, 80),
     makewindow(2, 32, 7, " SPIDER", 0, 5, 10, 60),
     makewindow(4, 16, 6, " S E P", 11, 0, 14, 40),
     makewindow(5, 96, 1, " N   C", 11, 41, 14, 38),
     ASSERT(responses("HOW ARE YOU?")),
     ASSERT(responses("CAN I HELP YOU?")),
     ASSERT(responses("DO YOU HAVE SOME PROBLEMS?")),
     ASSERT(responses("GO ON")),
     ASSERT(responses("WHAT KIND OF PROBLEM DO YOU HAVE?")),
     ASSERT(temp("")),
     ASSERT(translate("UNHAPPY", "WHY ARE YOU UNHAPY?")),
     ASSERT(translate("THINK", "WHY DO YOU THINK THAT?")),
     ASSERT(translate("YOU", "LET'S NOT TALK ABOUT ME.")),
     ASSERT(translate("IMPORTANT", "PLEASE TELL ME MORE.")),
     ASSERT(translate("WHAT", "WHY DO YOU ASK?")),
     ASSERT(translate("WANT", "WHAT WOULD YOU DO WITH IT?")),

     ASSERT(translate("NEED", "OK TELL ME MORE.")),
     ASSERT(translate("WHY", "REMEMBER, THIS IS THE ONLY ONE POLICY TO WIN THE WAR.")),
     ASSERT(translate("KNOW", "YOU HAVE TO OBEY THE COMMANDER'S RULE")),
     ASSERT(translate("ENEMY", "YOU ARE A GOOD COMMANDER.")),
     ASSERT(translate("KILL", "THAT IS GOOD YOU HAVE TO KILL ENEMIES")),
     ASSERT(translate("HURRY", "YOU HAVE TO HURRY!")),
     ASSERT(translate("NEVER", "DON'T WORRY; GOD HELP US ALWAYS!")),
     ASSERT(translate("FAILURE", "MAKE IT A SUCCESS")),

     CONVERSE.
     CLAUSES
     CONVERSE :-
        TEMP(S),
        S=quip,
        PURGE.
     Converse :-
        retract(temp(S)),
        DOCTOR(S),
        write(":"),
        readln(P),
        PATIENT(P),
        converse.

     DOCTOR("") :-
    /** NO RESPONSE FROM PATIENT
        CHECK PATIENT RESPONSE FILE **/
      RETRACT(topics(R)),
      R<>"",
      WRITE("You Just Said", ""),
      WRITE(R, ""), NL,
      WRITE("TELL ME MORE .."), NL.

     DOCTOR(S) :-
      RESPOND(S),
      ASSERT(topics(S)),
      NL.

     DOCTOR(_) :-
     /* NO RESPONSE FROM PATIENT
        CHECK PATIENT RESPONSE FILE */
     RETRACT(topics(R)),
     R<>"", WRITE("YOU JUST SAID", ""),
     WRITE(R, ""), NL,
     WRITE("TELL ME MORE..."), NL.
     RESPOND("") :-FAIL.
     RESPOND(S) :-
      FRONTTOKEN(S, T, _),
      TRANSLATE(T, T2),
      WRITE(T2, " ").
     RESPOND(S) :-
      FRONTTOKEN(S, _, S2),
      RESPOND(S2).
     PATIENT(S) :-
      PATRESPONSES(S),
      WRITE("YOU ARE REPEATING YOURSELF."),
      NL,
      WRITE(":"),
      readln(S2),
      PATIENT(S2),
     PATIENT(S) :-
      PATRESPONSES(S),
      WRITE("YOU ARE REPEATING YOURSELF."),
      NL,
      WRITE(":"),

      readln(S2),
     PATIENT(S) :-
      ASSERT(patresponses(S)),
      ASSERT(temp(S)).

     PURGE :-
      RETRACT(translate(_,_)),
      FAIL.
     PURGE :-
      RETRACT(temp(_)), FAIL.
     PURGE :-
      retract(responses(_)), fail.
     PURGE :-
      RETRACT(topics(_)), FAIL.
     PURGE :-
      RETRACT(patresponses(_)), FAIL.
     PURGE.


     ******************
     PROGRAM OUTPUT
     ******************
 

     HOW ARE YOU THIS BEAUTIFUL DAY?
      : I DON'T KNOW, I FEEL BAD.
     HOW DO YOU KNOW THAT?
      : I FEEL LIKE I COULD KILL
     IT IS WRONG TO KILL
      : I NEVER SEEM TO WIN
     DON'T BE NEGATIVE; THINK POSITIVE!
      :WHY
     REMEMBER, IT IS GOOD FOR YOU.
      :WHY
     YOU ARE REPEATING YOURSELF.
      :HEY, WHO CARES JERK
     DON'T YOU EVER CALL ME A JERK!
      :I'LL CALL YOU A JERK ANY TIME I WANT TO
     LET'S NOT TALK ABOUT ME.
      :I AM UNHAPPY
     WHY ARE YOU UNHAPPY?