Сибирский университет потребительской кооперации
Опубликован: 04.05.2005 | Доступ: свободный | Студентов: 4321 / 1347 | Оценка: 4.45 / 4.22 | Длительность: 12:28:00
ISBN: 978-5-9556-0034-5
Лекция 14:

Пролог и искусственный интеллект

< Лекция 13 || Лекция 14: 123

Усовершенствовать работу этой программы можно двумя способами. С одной стороны, можно увеличить количество шаблонов, с другой стороны, можно организовать разные реакции на некоторые из шаблонов (например, используя случайные числа).

В 1977 г. Кеннет Колби, основываясь на принципах организации "Элизы", создал программу, которая подобным образом вводила в заблуждение уже не клиентов психиатров, а самих докторов. Большинство из них после общения с его программой решили, что имели дело с реальным параноиком.

В 1996 г. Грег Гарви создал программную модель католического исповедника, которая опиралась на те же идеи, что и "Элиза".

Другие варианты "Элизы" можно найти в следующих книгах:

  • Л. Стерлинг, Э. Шапиро. Искусство программирования на языке Пролог. — М.:Мир, 1990.
  • Д. Марселлус. Программирование экспертных систем на Турбо-Прологе. — М.: Финансы и статистика, 1994.

Теперь рассмотрим еще один пример применения Пролога в области искусственного интеллекта. Создадим небольшую экспертную систему. Экспертными системами обычно называют программы, которые могут заменить эксперта в какой-то предметной области. Мы построим классификационную экспертную систему, которая будет пытаться угадать загаданное человеком животное. Если загаданное человеком животное окажется неизвестно нашей программе, у нее будет возможность пополнить свою базу знаний новой информацией.

В связи с тем, что мы планируем пополнять нашу базу знаний, мы будем по окончании работы сохранять ее в файл, а при начале работы считывать информацию из файла в оперативную память. Для этого мы воспользуемся изученными в предыдущей лекции внутренними (динамическими) базами данных.

Так как в Турбо Прологе в базе данных можно размещать только факты, представим правила, определяющие животных в виде фактов.

Определим два предиката внутренней базы данных, которые позволят нам хранить информацию о животных.

Один из них предназначен для хранения характеристик животных и будет иметь два аргумента: первый — номер свойства, второй — его словесное описание.

Небольшой базовый набор свойств может выглядеть, например, так:

cond(1,"кормит детенышей молоком").
cond(2,"имеет перья").
cond(3,"плавает").
cond(4,"ест мясо").
cond(5,"имеет копыта").
cond(6,"летает").
cond(7,"откладывает яйца").
cond(8,"имеет шерсть").
cond(9,"имеет полосы").
cond(10,"имеет пятна").
cond(11,"имеет черно-белую окраску").
cond(12,"имеет длинную шею").
cond(13,"имеет длинные ноги").
cond(14,"имеет щупальца").

Второй предикат будет хранить описание животных. Первый его аргумент — название животного, второй — список, элементами которого являются номера свойств, присущих данному животному.

Выглядеть эта база знаний может примерно следующим образом:

rule("гепард",[1,4,8,10]).
rule("тигр",[1,4,8,9]).
rule("жираф",[1,5,8,10,12,13]).
rule("зебра",[1,5,8,9,11]).
rule("страус",[2,14]).
rule("пингвин",[2,3,11]).
rule("орел",[2,6]).
rule("кит",[1,3,11]).
rule("осьминог",[3,14]).

По сути дела, в виде фактов записаны правила. Например, правило: "если животное кормит детенышей молоком, имеет копыта, пятна, длинную шею и ноги, то это жираф", записано в виде rule("жираф", [1,5,11,13,14]).

Во второй базе мы будем хранить ответы человека в следующем виде:

cond_is(N,'1') /*	если загаданное животное имеет свойство 
         с номером N */
cond_is(N,'2') /* 	если загаданное животное не имеет 
         свойства с номером N */

Первую базу назовем knowledge, а вторую — dialog.

Процесс отгадывания задуманного животного будет проходить следующим образом. Будем последовательно перебирать животных, имеющихся в нашей базе знаний. Если загаданное животное обладает всеми характеристиками известного программе животного, делается вывод о том, кто был загадан. Если не удается сопоставить загаданному животному ни одно из животных, имеющихся в базе знаний, производится пополнение базы.

Вот как будет выглядеть реализация написанного выше.

animals:–
        rule(X,L),
        check(L),
        nl,write("Я думаю это ",X),
        nl,write("Я прав? (1 — да, 2 — нет)"),
        read_true_char(C),C='1',!.
animals:–
        nl,write("Я не знаю, что это за животное"),nl, 
        nl,write("Давайте добавим его в мою базу 
        знаний."),nl,
        update.

Предикат check осуществляет проверку свойств, номера которых входят в список, указанный в качестве его единственного аргумента.

check([H|T]):–
             test_cond(H), 
             check(T).
check([]).

Предикат test_cond проверяет наличие у загаданного животного свойства с номером, указанным в качестве его единственного аргумента. Если человеком ранее уже был дан ответ (положительный или отрицательный) по поводу наличия данного свойства, информация об этом имеется в базе данных. Если же в базе нет никакой информации о наличии данного свойства у загаданного животного, нужно задать человеку соответствующий вопрос и добавить его ответ в базу.

Вот как это можно записать.

test_cond(H):–
             cond_is(H,'1'),!. /* в базе имеется 
            информация о наличии 
            данного свойства */
test_cond(H):–
             cond_is(H,'2'),!,
             fail. /* в базе имеется информация 
           об отсутствии	данного свойства */
test_cond(H):– /* в базе нет никакой информации о данном 
       свойстве, получаем ее у человека */
             cond(H,S),
             nl,write("Оно ",S,"? (1 — да, 2 — нет)"),
             read_true_char(A),
             assert(cond_is(H,A)),
             test_cond(H).

Предикат read_true_char осуществляет проверку нажатой пользователем клавиши, и если она отлична от '1' или '2', выводит соответствующее сообщение и повторно считывает символ с клавиатуры.

read_true_char(C):–
       readchar(C1),
       test(C1,C).
test(C,C):–
          '1'<=C,C<='2',!.
test(_,C):–
          write("Нажмите 1 или 2!"),nl,
          readchar(C1),
          test(C1,C).

Предикат update осуществляет добавление новой информации в базу знаний. Он читает название нового животного, с помощью предиката add_cond формирует список номеров свойств загаданного животного, добавляет соответствующий факт в базу знаний, сохраняет базу в файл.

Вот как он будет выглядеть:

update:–
       nl,write("Введите название животного:"),
       readln(S),
       add_cond(L), /* указываем свойства животного */
       assert(rule(S,L),knowledge), 
         /* добавляем информацию в базу 
            знаний*/
       save("animals.ddb",knowledge) 
         /* сохраняем базу знаний в файл */.

Предикат add_cond с помощью предиката print_cond выводит уже имеющуюся в базе информацию о свойствах загаданного животного и спрашивает, известно ли еще что-нибудь о нем. В случае необходимости добавляет его новые характеристики, используя предикат read_cond.

add_cond(L):–
             cond_is(_,'1'),!, 
             /* имеется информация о свойствах 
                животного */
             nl,write("О нем известно, что оно: "),
             print_cond(1,[],L1), 
             /* вывод имеющейся информации 
                о животном */
             nl,write("Известно ли о нем еще что-нибудь?
             (1 — да, 2 — нет)"),
             read_true_char(C),
             read_cond(C,L1,L).
add_cond(L):–
             read_cond('1',[],L).

Предикат read_cond, используя предикат ex_cond, добавляет в список номера свойств, уже имеющихся в базе; используя предикат new_cond, добавляет в список номера новых свойств, а также описания самих свойств — в базу знаний.

read_cond('1',L,L2):–
          ex_cond(1,L,L1,N),
          new_cond(N,L1,L2),!. 
read_cond(_,L,L):–!.

Основные предикаты мы рассмотрели, а вот как будет выглядеть вся программа целиком:

DOMAINS
i=integer
s=string
c=char
li=i* /* список целых чисел */
DATABASE — knowledge
cond(i,s) /* свойства животных */
rule(s,li) /* описания животных */
DATABASE — dialog
cond_is(i,c) /* номер условия; '1' — имеет место, 
     '2' — не имеет	места у загаданного 
     животного */
PREDICATES
start
animals /* отгадывает животное */
check(li) /* добавляет в базу информацию о новом 
             животном */
test_cond(i) /* проверяет, обладает ли загаданное 
     животное свойством с данным номером */
update /* добавляет в базу информацию о новом животном */
add_cond(li) /* возвращает список, состоящий из номеров 
            свойств, имеющихся у нового животного */
print_cond(i,li,li) /* добавляет в список номера свойств, 
            относительно которых уже были даны 
            утвердительные ответы */
read_cond(c,li,li) /* добавляет в список номера свойств, 
           о которых еще не спрашивалось */
ex_cond(i,li,li,i) /* добавляет в список номера имеющихся 
           в базе свойств, которыми обладает 
           новое животное */
wr_cond(c,i,li,li)
new_cond(i,li,li) /* добавляет в список номера новых 
          свойств, которыми обладает новое 
          животное, а также добавляет описания 
          новых свойств в базу знаний */
read_true_char(c) /* с помощью предиката test читает 
          символ с клавиатуры, пока он 
          не окажется равен '1' или '2'*/
test(c,c) /* добивается, чтобы пользователь нажал один 
             из символов, '1' или '2' */
CLAUSES
start:–
       consult("animals.ddb",knowledge), 
              /*  загружаем в базу информацию 
                  из базы знаний */
       write("Загадайте животное, а я попытаюсь его 
       отгадать"),nl,
       animals, /* попытка отгадать загаданное животное */
       retractall(_,dialog), /* очищаем текущую 
          информацию */
       retractall(_,knowledge), 
                  /* очищаем информацию об известных 
          животных и свойствах */
       nl,nl,write("Хотите еще раз сыграть? (1 — Да, 
       2 — Нет)"),
       read_true_char(C),
       C='1',!,start. 
start:–
       nl,nl,write("Всего доброго! До новых встреч"),
       readchar(_).
animals:–
        rule(X,L),
        check(L),
        nl,write("Я думаю, это ",X),
        nl,write("Я прав? (1 — да, 2 — нет)"),
        read_true_char(C),C='1',!.
animals:–
        nl,write("Я не знаю, что это за животное"),nl, 
        nl,write("Давайте добавим его в мою базу знаний."),nl,
        update.
update:–
        nl,write("Введите название животного:"),
        readln(S),
        add_cond(L), /* указываем свойства животного */
        assert(rule(S,L),knowledge), /* добавляем информацию 
                  в базу знаний*/
        save("animals.ddb",knowledge) /* сохраняем базу 
                   знаний в файл */.
add_cond(L):–
             cond_is(_,'1'),!, /* имеется информация 
            о свойствах животного */
             nl,write("О нем известно, что оно: "),
             print_cond(1,[],L1), 
         /* вывод имеющейся о животном 
            информации */
             nl,write("Известно ли о нем еще что-нибудь? 
             (1 — да, 2 — нет)"),
             read_true_char(C),
             read_cond(C,L1,L).
add_cond(L):–
             read_cond('1',[],L).
print_cond(H,L,L):–
        not(cond(H,_)),!.
print_cond(H,L,L1):–
         cond_is(H,'1'),!,
         cond(H,T),
         H1=H+1,
         nl,write(T),
         print_cond(H1,[H	L],L1).
print_cond(H,L,L1):–
         H1=H+1,
         print_cond(H1,L,L1).
read_cond('1',L,L2):–
          ex_cond(1,L,L1,N),
          new_cond(N,L1,L2),!. 
read_cond(_,L,L):–!.
ex_cond(N,L,L,N):–
       not(cond(N,_)),!.
ex_cond(N,L,L1,N2):–
         cond_is(N,_),!,
         N1=N+1,
         ex_cond(N1,L,L1,N2).
ex_cond(N,L,L1,N2):–
         cond(N,S),
         nl,write("Оно ",S,"? (1 — да, 2 — нет)"),
         read_true_char(A),
         wr_cond(A,N,L,L2),
         N1=N+1,
         ex_cond(N1,L2,L1,N2).
wr_cond('1',N,L,[N	L]):–!.
wr_cond('2',_,L,L):–!.
new_cond(N,L,L1):–
       nl,write("Есть еще свойства? (1 — да, 
       2– нет)"),
       read_true_char(A),
       A='1',!,
       nl,write("Укажите новое свойство, 
       которым обладает животное"),
       nl,write("в виде 'оно <описание 
       нового свойства>'"), readln(S),
       assertz(cond(N,S)), 
                 /* добавление нового свойства 
         в базу знаний */
       N1=N+1,
       new_cond(N1,[N	L],L1).
new_cond(_,L,L).
check([HT]):–
              test_cond(H), 
              check(T).
check([]).
test_cond(H):-
              cond_is(H,'1'),!. /* в базе имеется информация 
             о наличии свойства */
test_cond(H):–
              cond_is(H,'2'),!,
              fail. /* в базе имеется информация 
           об отсутствии свойства */
test_cond(H):– /* в базе нет никакой информации о данном 
       свойстве, получаем ее у человека */
              cond(H,S),
              nl,write("Оно ",S,"? (1 — да, 2 — нет)"),
              read_true_char(A),
              assert(cond_is(H,A)),
              test_cond(H).
read_true_char(C):–
        readchar(C1),
        test(C1,C).
test(C,C):–
           '1'<=C,C<='2',!.
test(_,C):–
           write("Нажмите 1 или 2!"),nl,
           readchar(C1),
           test(C1,C). 
GOAL
start
Листинг 14.2. Самообучающийся определитель животных

В идеале экспертная система должна уметь объяснять пользователю свое решение, а также почему она задает тот или иной вопрос. Попробуйте добавить в нашу экспертную систему механизм объяснения.

Две версии этой программы, основанной на правилах (реализующие, соответственно, прямую и обратную цепочки рассуждений), можно найти в книге Д. Марселлус, Программирование экспертных систем на Турбо-Прологе. — М.: Финансы и статистика, 1994. Однако эти программы умеют распознавать только тех животных, которые заложены в них изначально. Возможности динамического пополнения базы знаний они не имеют. Для добавления описания нового животного требуется модификация программного кода.

Кроме того, я рекомендую читателям изучить программу GEOBASE, которая входит в состав и Турбо Пролога, и Visual Prolog. Эта программа содержит информацию по географии США и позволяет создавать запросы к базе данных на естественном (английском) языке.

< Лекция 13 || Лекция 14: 123
Виктор Бондарь
Виктор Бондарь

После приведения формулы вида ПНФ к виду ССФ вы получаете формулу, в безквантовой матрице которой дизъюнкт содержит оба контранрных атома:. Как тогда проводить его унификацию, если в случае замены x на f(x) весь дизъюнкт обратится в единицу?

Ольга Потапенко
Ольга Потапенко

никак не могу увидеть тексты самих лекций.